mirror of
https://github.com/bluekeyes/go-gitdiff
synced 2026-06-08 13:18:30 +00:00
14da3d30f7
Some patches may include one or more file paths as part of the binary header when there is no binary data. Git accounts for this by only checking the prefix and suffix of the line, but I missed that logic when implementing this originally.
325 lines
7.1 KiB
Go
325 lines
7.1 KiB
Go
package gitdiff
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseBinaryMarker(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Input string
|
|
IsBinary bool
|
|
HasData bool
|
|
Err bool
|
|
}{
|
|
"binaryPatch": {
|
|
Input: "GIT binary patch\n",
|
|
IsBinary: true,
|
|
HasData: true,
|
|
},
|
|
"binaryFileNoPatch": {
|
|
Input: "Binary files differ\n",
|
|
IsBinary: true,
|
|
HasData: false,
|
|
},
|
|
"binaryFileNoPatchPaths": {
|
|
Input: "Binary files a/foo.bin and b/foo.bin differ\n",
|
|
IsBinary: true,
|
|
HasData: false,
|
|
},
|
|
"fileNoPatch": {
|
|
Input: "Files differ\n",
|
|
IsBinary: true,
|
|
HasData: false,
|
|
},
|
|
"textFile": {
|
|
Input: "@@ -10,14 +22,31 @@\n",
|
|
IsBinary: false,
|
|
HasData: false,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
p := newTestParser(test.Input, true)
|
|
|
|
isBinary, hasData, err := p.ParseBinaryMarker()
|
|
if test.Err {
|
|
if err == nil || err == io.EOF {
|
|
t.Fatalf("expected error parsing binary marker, but got %v", err)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error parsing binary marker: %v", err)
|
|
}
|
|
if test.IsBinary != isBinary {
|
|
t.Errorf("incorrect isBinary value: expected %t, actual %t", test.IsBinary, isBinary)
|
|
}
|
|
if test.HasData != hasData {
|
|
t.Errorf("incorrect hasData value: expected %t, actual %t", test.HasData, hasData)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseBinaryFragmentHeader(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Input string
|
|
Output *BinaryFragment
|
|
Err bool
|
|
}{
|
|
"delta": {
|
|
Input: "delta 1234\n",
|
|
Output: &BinaryFragment{
|
|
Method: BinaryPatchDelta,
|
|
Size: 1234,
|
|
},
|
|
},
|
|
"literal": {
|
|
Input: "literal 1234\n",
|
|
Output: &BinaryFragment{
|
|
Method: BinaryPatchLiteral,
|
|
Size: 1234,
|
|
},
|
|
},
|
|
"unknownMethod": {
|
|
Input: "compressed 1234\n",
|
|
Output: nil,
|
|
},
|
|
"notAHeader": {
|
|
Input: "Binary files differ\n",
|
|
Output: nil,
|
|
},
|
|
"invalidSize": {
|
|
Input: "delta 123abc\n",
|
|
Err: true,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
p := newTestParser(test.Input, true)
|
|
|
|
frag, err := p.ParseBinaryFragmentHeader()
|
|
if test.Err {
|
|
if err == nil || err == io.EOF {
|
|
t.Fatalf("expected error parsing binary header, but got %v", err)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error parsing binary header: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(test.Output, frag) {
|
|
t.Errorf("incorrect binary fragment\nexpected: %+v\n actual: %+v", test.Output, frag)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseBinaryChunk(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Input string
|
|
Fragment BinaryFragment
|
|
Output []byte
|
|
Err string
|
|
}{
|
|
"singleline": {
|
|
Input: "TcmZQzU|?i`U?w2V48*Je09XJG\n\n",
|
|
Fragment: BinaryFragment{
|
|
Size: 20,
|
|
},
|
|
Output: fib(5, binary.BigEndian),
|
|
},
|
|
"multiline": {
|
|
Input: "zcmZQzU|?i`U?w2V48*KJ%mKu_Kr9NxN<eH5#F0Qe0f=7$l~*z_FeL$%-)3N7vt?l5\n" +
|
|
"zl3-vE2xVZ9%4J~CI>f->s?WfX|B-=Vs{#X~svra7Ekg#T|4s}nH;WnAZ)|1Y*`&cB\n" +
|
|
"s(sh?X(Uz6L^!Ou&aF*u`J!eibJifSrv0z>$Q%Hd(^HIJ<Y?5`S0gT5UE&u=k\n\n",
|
|
Fragment: BinaryFragment{
|
|
Size: 160,
|
|
},
|
|
Output: fib(40, binary.BigEndian),
|
|
},
|
|
"shortLine": {
|
|
Input: "A00\n\n",
|
|
Err: "corrupt data line",
|
|
},
|
|
"underpaddedLine": {
|
|
Input: "H00000000\n\n",
|
|
Err: "corrupt data line",
|
|
},
|
|
"invalidLengthByte": {
|
|
Input: "!00000\n\n",
|
|
Err: "invalid length byte",
|
|
},
|
|
"miscountedLine": {
|
|
Input: "H00000\n\n",
|
|
Err: "incorrect byte count",
|
|
},
|
|
"invalidEncoding": {
|
|
Input: "TcmZQzU|?i'U?w2V48*Je09XJG\n",
|
|
Err: "invalid base85 byte",
|
|
},
|
|
"noTrailingEmptyLine": {
|
|
Input: "TcmZQzU|?i`U?w2V48*Je09XJG\n",
|
|
Err: "unexpected EOF",
|
|
},
|
|
"invalidCompression": {
|
|
Input: "F007GV%KiWV\n\n",
|
|
Err: "zlib",
|
|
},
|
|
"incorrectSize": {
|
|
Input: "TcmZQzU|?i`U?w2V48*Je09XJG\n\n",
|
|
Fragment: BinaryFragment{
|
|
Size: 16,
|
|
},
|
|
Err: "16 byte fragment inflated to 20",
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
p := newTestParser(test.Input, true)
|
|
|
|
frag := test.Fragment
|
|
err := p.ParseBinaryChunk(&frag)
|
|
if test.Err != "" {
|
|
if err == nil || !strings.Contains(err.Error(), test.Err) {
|
|
t.Fatalf("expected error containing %q parsing binary chunk, but got %v", test.Err, err)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error parsing binary chunk: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(test.Output, frag.Data) {
|
|
t.Errorf("incorrect binary chunk\nexpected: %+v\n actual: %+v", test.Output, frag.Data)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseBinaryFragments(t *testing.T) {
|
|
tests := map[string]struct {
|
|
Input string
|
|
File File
|
|
|
|
Binary bool
|
|
Fragment *BinaryFragment
|
|
ReverseFragment *BinaryFragment
|
|
Err bool
|
|
}{
|
|
"dataWithReverse": {
|
|
Input: `GIT binary patch
|
|
literal 40
|
|
gcmZQzU|?i` + "`" + `U?w2V48*KJ%mKu_Kr9NxN<eH500b)lkN^Mx
|
|
|
|
literal 0
|
|
HcmV?d00001
|
|
|
|
`,
|
|
Binary: true,
|
|
Fragment: &BinaryFragment{
|
|
Method: BinaryPatchLiteral,
|
|
Size: 40,
|
|
Data: fib(10, binary.BigEndian),
|
|
},
|
|
ReverseFragment: &BinaryFragment{
|
|
Method: BinaryPatchLiteral,
|
|
Size: 0,
|
|
Data: []byte{},
|
|
},
|
|
},
|
|
"dataWithoutReverse": {
|
|
Input: `GIT binary patch
|
|
literal 40
|
|
gcmZQzU|?i` + "`" + `U?w2V48*KJ%mKu_Kr9NxN<eH500b)lkN^Mx
|
|
|
|
`,
|
|
Binary: true,
|
|
Fragment: &BinaryFragment{
|
|
Method: BinaryPatchLiteral,
|
|
Size: 40,
|
|
Data: fib(10, binary.BigEndian),
|
|
},
|
|
},
|
|
"noData": {
|
|
Input: "Binary files differ\n",
|
|
Binary: true,
|
|
},
|
|
"text": {
|
|
Input: `@@ -1 +1 @@
|
|
-old line
|
|
+new line
|
|
`,
|
|
Binary: false,
|
|
},
|
|
"missingData": {
|
|
Input: "GIT binary patch\n",
|
|
Err: true,
|
|
},
|
|
"invalidData": {
|
|
Input: `GIT binary patch
|
|
literal 20
|
|
TcmZQzU|?i'U?w2V48*Je09XJG
|
|
|
|
`,
|
|
Err: true,
|
|
},
|
|
"invalidReverseData": {
|
|
Input: `GIT binary patch
|
|
literal 20
|
|
TcmZQzU|?i` + "`" + `U?w2V48*Je09XJG
|
|
|
|
literal 0
|
|
zcmV?d00001
|
|
|
|
`,
|
|
Err: true,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
p := newTestParser(test.Input, true)
|
|
|
|
file := test.File
|
|
_, err := p.ParseBinaryFragments(&file)
|
|
if test.Err {
|
|
if err == nil || err == io.EOF {
|
|
t.Fatalf("expected error parsing binary fragments, but got %v", err)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error parsing binary fragments: %v", err)
|
|
}
|
|
if test.Binary != file.IsBinary {
|
|
t.Errorf("incorrect binary state: expected %t, actual %t", test.Binary, file.IsBinary)
|
|
}
|
|
if !reflect.DeepEqual(test.Fragment, file.BinaryFragment) {
|
|
t.Errorf("incorrect binary fragment\nexpected: %+v\n actual: %+v", test.Fragment, file.BinaryFragment)
|
|
}
|
|
if !reflect.DeepEqual(test.ReverseFragment, file.ReverseBinaryFragment) {
|
|
t.Errorf("incorrect reverse binary fragment\nexpected: %+v\n actual: %+v", test.ReverseFragment, file.ReverseBinaryFragment)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func fib(n int, ord binary.ByteOrder) []byte {
|
|
buf := make([]byte, 4*n)
|
|
for i := 0; i < len(buf); i += 4 {
|
|
if i < 8 {
|
|
ord.PutUint32(buf[i:], 1)
|
|
} else {
|
|
ord.PutUint32(buf[i:], ord.Uint32(buf[i-4:])+ord.Uint32(buf[i-8:]))
|
|
}
|
|
}
|
|
return buf
|
|
}
|