master
  1package gitdiff
  2
  3import (
  4	"encoding/binary"
  5	"io"
  6	"reflect"
  7	"strings"
  8	"testing"
  9)
 10
 11func TestParseBinaryMarker(t *testing.T) {
 12	tests := map[string]struct {
 13		Input    string
 14		IsBinary bool
 15		HasData  bool
 16		Err      bool
 17	}{
 18		"binaryPatch": {
 19			Input:    "GIT binary patch\n",
 20			IsBinary: true,
 21			HasData:  true,
 22		},
 23		"binaryFileNoPatch": {
 24			Input:    "Binary files differ\n",
 25			IsBinary: true,
 26			HasData:  false,
 27		},
 28		"binaryFileNoPatchPaths": {
 29			Input:    "Binary files a/foo.bin and b/foo.bin differ\n",
 30			IsBinary: true,
 31			HasData:  false,
 32		},
 33		"fileNoPatch": {
 34			Input:    "Files differ\n",
 35			IsBinary: true,
 36			HasData:  false,
 37		},
 38		"textFile": {
 39			Input:    "@@ -10,14 +22,31 @@\n",
 40			IsBinary: false,
 41			HasData:  false,
 42		},
 43	}
 44
 45	for name, test := range tests {
 46		t.Run(name, func(t *testing.T) {
 47			p := newTestParser(test.Input, true)
 48
 49			isBinary, hasData, err := p.ParseBinaryMarker()
 50			if test.Err {
 51				if err == nil || err == io.EOF {
 52					t.Fatalf("expected error parsing binary marker, but got %v", err)
 53				}
 54				return
 55			}
 56			if err != nil {
 57				t.Fatalf("unexpected error parsing binary marker: %v", err)
 58			}
 59			if test.IsBinary != isBinary {
 60				t.Errorf("incorrect isBinary value: expected %t, actual %t", test.IsBinary, isBinary)
 61			}
 62			if test.HasData != hasData {
 63				t.Errorf("incorrect hasData value: expected %t, actual %t", test.HasData, hasData)
 64			}
 65		})
 66	}
 67}
 68
 69func TestParseBinaryFragmentHeader(t *testing.T) {
 70	tests := map[string]struct {
 71		Input  string
 72		Output *BinaryFragment
 73		Err    bool
 74	}{
 75		"delta": {
 76			Input: "delta 1234\n",
 77			Output: &BinaryFragment{
 78				Method: BinaryPatchDelta,
 79				Size:   1234,
 80			},
 81		},
 82		"literal": {
 83			Input: "literal 1234\n",
 84			Output: &BinaryFragment{
 85				Method: BinaryPatchLiteral,
 86				Size:   1234,
 87			},
 88		},
 89		"unknownMethod": {
 90			Input:  "compressed 1234\n",
 91			Output: nil,
 92		},
 93		"notAHeader": {
 94			Input:  "Binary files differ\n",
 95			Output: nil,
 96		},
 97		"invalidSize": {
 98			Input: "delta 123abc\n",
 99			Err:   true,
100		},
101	}
102
103	for name, test := range tests {
104		t.Run(name, func(t *testing.T) {
105			p := newTestParser(test.Input, true)
106
107			frag, err := p.ParseBinaryFragmentHeader()
108			if test.Err {
109				if err == nil || err == io.EOF {
110					t.Fatalf("expected error parsing binary header, but got %v", err)
111				}
112				return
113			}
114			if err != nil {
115				t.Fatalf("unexpected error parsing binary header: %v", err)
116			}
117			if !reflect.DeepEqual(test.Output, frag) {
118				t.Errorf("incorrect binary fragment\nexpected: %+v\n  actual: %+v", test.Output, frag)
119			}
120		})
121	}
122}
123
124func TestParseBinaryChunk(t *testing.T) {
125	tests := map[string]struct {
126		Input    string
127		Fragment BinaryFragment
128		Output   []byte
129		Err      string
130	}{
131		"singleline": {
132			Input: "TcmZQzU|?i`U?w2V48*Je09XJG\n\n",
133			Fragment: BinaryFragment{
134				Size: 20,
135			},
136			Output: fib(5, binary.BigEndian),
137		},
138		"multiline": {
139			Input: "zcmZQzU|?i`U?w2V48*KJ%mKu_Kr9NxN<eH5#F0Qe0f=7$l~*z_FeL$%-)3N7vt?l5\n" +
140				"zl3-vE2xVZ9%4J~CI>f->s?WfX|B-=Vs{#X~svra7Ekg#T|4s}nH;WnAZ)|1Y*`&cB\n" +
141				"s(sh?X(Uz6L^!Ou&aF*u`J!eibJifSrv0z>$Q%Hd(^HIJ<Y?5`S0gT5UE&u=k\n\n",
142			Fragment: BinaryFragment{
143				Size: 160,
144			},
145			Output: fib(40, binary.BigEndian),
146		},
147		"shortLine": {
148			Input: "A00\n\n",
149			Err:   "corrupt data line",
150		},
151		"underpaddedLine": {
152			Input: "H00000000\n\n",
153			Err:   "corrupt data line",
154		},
155		"invalidLengthByte": {
156			Input: "!00000\n\n",
157			Err:   "invalid length byte",
158		},
159		"miscountedLine": {
160			Input: "H00000\n\n",
161			Err:   "incorrect byte count",
162		},
163		"invalidEncoding": {
164			Input: "TcmZQzU|?i'U?w2V48*Je09XJG\n",
165			Err:   "invalid base85 byte",
166		},
167		"noTrailingEmptyLine": {
168			Input: "TcmZQzU|?i`U?w2V48*Je09XJG\n",
169			Err:   "unexpected EOF",
170		},
171		"invalidCompression": {
172			Input: "F007GV%KiWV\n\n",
173			Err:   "zlib",
174		},
175		"incorrectSize": {
176			Input: "TcmZQzU|?i`U?w2V48*Je09XJG\n\n",
177			Fragment: BinaryFragment{
178				Size: 16,
179			},
180			Err: "16 byte fragment inflated to 20",
181		},
182	}
183
184	for name, test := range tests {
185		t.Run(name, func(t *testing.T) {
186			p := newTestParser(test.Input, true)
187
188			frag := test.Fragment
189			err := p.ParseBinaryChunk(&frag)
190			if test.Err != "" {
191				if err == nil || !strings.Contains(err.Error(), test.Err) {
192					t.Fatalf("expected error containing %q parsing binary chunk, but got %v", test.Err, err)
193				}
194				return
195			}
196			if err != nil {
197				t.Fatalf("unexpected error parsing binary chunk: %v", err)
198			}
199			if !reflect.DeepEqual(test.Output, frag.Data) {
200				t.Errorf("incorrect binary chunk\nexpected: %+v\n  actual: %+v", test.Output, frag.Data)
201			}
202		})
203	}
204}
205
206func TestParseBinaryFragments(t *testing.T) {
207	tests := map[string]struct {
208		Input string
209		File  File
210
211		Binary          bool
212		Fragment        *BinaryFragment
213		ReverseFragment *BinaryFragment
214		Err             bool
215	}{
216		"dataWithReverse": {
217			Input: `GIT binary patch
218literal 40
219gcmZQzU|?i` + "`" + `U?w2V48*KJ%mKu_Kr9NxN<eH500b)lkN^Mx
220
221literal 0
222HcmV?d00001
223
224`,
225			Binary: true,
226			Fragment: &BinaryFragment{
227				Method: BinaryPatchLiteral,
228				Size:   40,
229				Data:   fib(10, binary.BigEndian),
230			},
231			ReverseFragment: &BinaryFragment{
232				Method: BinaryPatchLiteral,
233				Size:   0,
234				Data:   []byte{},
235			},
236		},
237		"dataWithoutReverse": {
238			Input: `GIT binary patch
239literal 40
240gcmZQzU|?i` + "`" + `U?w2V48*KJ%mKu_Kr9NxN<eH500b)lkN^Mx
241
242`,
243			Binary: true,
244			Fragment: &BinaryFragment{
245				Method: BinaryPatchLiteral,
246				Size:   40,
247				Data:   fib(10, binary.BigEndian),
248			},
249		},
250		"noData": {
251			Input:  "Binary files differ\n",
252			Binary: true,
253		},
254		"text": {
255			Input: `@@ -1 +1 @@
256-old line
257+new line
258`,
259			Binary: false,
260		},
261		"missingData": {
262			Input: "GIT binary patch\n",
263			Err:   true,
264		},
265		"invalidData": {
266			Input: `GIT binary patch
267literal 20
268TcmZQzU|?i'U?w2V48*Je09XJG
269
270`,
271			Err: true,
272		},
273		"invalidReverseData": {
274			Input: `GIT binary patch
275literal 20
276TcmZQzU|?i` + "`" + `U?w2V48*Je09XJG
277
278literal 0
279zcmV?d00001
280
281`,
282			Err: true,
283		},
284	}
285
286	for name, test := range tests {
287		t.Run(name, func(t *testing.T) {
288			p := newTestParser(test.Input, true)
289
290			file := test.File
291			_, err := p.ParseBinaryFragments(&file)
292			if test.Err {
293				if err == nil || err == io.EOF {
294					t.Fatalf("expected error parsing binary fragments, but got %v", err)
295				}
296				return
297			}
298			if err != nil {
299				t.Fatalf("unexpected error parsing binary fragments: %v", err)
300			}
301			if test.Binary != file.IsBinary {
302				t.Errorf("incorrect binary state: expected %t, actual %t", test.Binary, file.IsBinary)
303			}
304			if !reflect.DeepEqual(test.Fragment, file.BinaryFragment) {
305				t.Errorf("incorrect binary fragment\nexpected: %+v\n  actual: %+v", test.Fragment, file.BinaryFragment)
306			}
307			if !reflect.DeepEqual(test.ReverseFragment, file.ReverseBinaryFragment) {
308				t.Errorf("incorrect reverse binary fragment\nexpected: %+v\n  actual: %+v", test.ReverseFragment, file.ReverseBinaryFragment)
309			}
310		})
311	}
312}
313
314func fib(n int, ord binary.ByteOrder) []byte {
315	buf := make([]byte, 4*n)
316	for i := 0; i < len(buf); i += 4 {
317		if i < 8 {
318			ord.PutUint32(buf[i:], 1)
319		} else {
320			ord.PutUint32(buf[i:], ord.Uint32(buf[i-4:])+ord.Uint32(buf[i-8:]))
321		}
322	}
323	return buf
324}