package packfile import ( "bytes" "io" "math/rand" "testing" "github.com/go-git/go-git/v5/plumbing" . "gopkg.in/check.v1" ) type DeltaSuite struct { testCases []deltaTest } var _ = Suite(&DeltaSuite{}) type deltaTest struct { description string base []piece target []piece } func (s *DeltaSuite) SetUpSuite(c *C) { s.testCases = []deltaTest{{ description: "distinct file", base: []piece{{"0", 300}}, target: []piece{{"2", 200}}, }, { description: "same file", base: []piece{{"1", 3000}}, target: []piece{{"1", 3000}}, }, { description: "small file", base: []piece{{"1", 3}}, target: []piece{{"1", 3}, {"0", 1}}, }, { description: "big file", base: []piece{{"1", 300000}}, target: []piece{{"1", 30000}, {"0", 1000000}}, }, { description: "add elements before", base: []piece{{"0", 200}}, target: []piece{{"1", 300}, {"0", 200}}, }, { description: "add 10 times more elements at the end", base: []piece{{"1", 300}, {"0", 200}}, target: []piece{{"0", 2000}}, }, { description: "add elements between", base: []piece{{"0", 400}}, target: []piece{{"0", 200}, {"1", 200}, {"0", 200}}, }, { description: "add elements after", base: []piece{{"0", 200}}, target: []piece{{"0", 200}, {"1", 200}}, }, { description: "modify elements at the end", base: []piece{{"1", 300}, {"0", 200}}, target: []piece{{"0", 100}}, }, { description: "complex modification", base: []piece{{"0", 3}, {"1", 40}, {"2", 30}, {"3", 2}, {"4", 400}, {"5", 23}}, target: []piece{{"1", 30}, {"2", 20}, {"7", 40}, {"4", 400}, {"5", 10}}, }, { description: "A copy operation bigger than 64kb", base: []piece{{bigRandStr, 1}, {"1", 200}}, target: []piece{{bigRandStr, 1}}, }} } var bigRandStr = randStringBytes(100 * 1024) const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randBytes(n int) []byte { b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } return b } func randStringBytes(n int) string { return string(randBytes(n)) } func (s *DeltaSuite) TestAddDelta(c *C) { for _, t := range s.testCases { baseBuf := genBytes(t.base) targetBuf := genBytes(t.target) delta := DiffDelta(baseBuf, targetBuf) result, err := PatchDelta(baseBuf, delta) c.Log("Executing test case:", t.description) c.Assert(err, IsNil) c.Assert(result, DeepEquals, targetBuf) } } func (s *DeltaSuite) TestAddDeltaReader(c *C) { for _, t := range s.testCases { baseBuf := genBytes(t.base) baseObj := &plumbing.MemoryObject{} baseObj.Write(baseBuf) targetBuf := genBytes(t.target) delta := DiffDelta(baseBuf, targetBuf) deltaRC := io.NopCloser(bytes.NewReader(delta)) c.Log("Executing test case:", t.description) resultRC, err := ReaderFromDelta(baseObj, deltaRC) c.Assert(err, IsNil) result, err := io.ReadAll(resultRC) c.Assert(err, IsNil) err = resultRC.Close() c.Assert(err, IsNil) c.Assert(result, DeepEquals, targetBuf) } } func (s *DeltaSuite) TestIncompleteDelta(c *C) { for _, t := range s.testCases { c.Log("Incomplete delta on:", t.description) baseBuf := genBytes(t.base) targetBuf := genBytes(t.target) delta := DiffDelta(baseBuf, targetBuf) delta = delta[:len(delta)-2] result, err := PatchDelta(baseBuf, delta) c.Assert(err, NotNil) c.Assert(result, IsNil) } // check nil input too result, err := PatchDelta(nil, nil) c.Assert(err, NotNil) c.Assert(result, IsNil) } func (s *DeltaSuite) TestMaxCopySizeDelta(c *C) { baseBuf := randBytes(maxCopySize) targetBuf := baseBuf[0:] targetBuf = append(targetBuf, byte(1)) delta := DiffDelta(baseBuf, targetBuf) result, err := PatchDelta(baseBuf, delta) c.Assert(err, IsNil) c.Assert(result, DeepEquals, targetBuf) } func (s *DeltaSuite) TestMaxCopySizeDeltaReader(c *C) { baseBuf := randBytes(maxCopySize) baseObj := &plumbing.MemoryObject{} baseObj.Write(baseBuf) targetBuf := baseBuf[0:] targetBuf = append(targetBuf, byte(1)) delta := DiffDelta(baseBuf, targetBuf) deltaRC := io.NopCloser(bytes.NewReader(delta)) resultRC, err := ReaderFromDelta(baseObj, deltaRC) c.Assert(err, IsNil) result, err := io.ReadAll(resultRC) c.Assert(err, IsNil) err = resultRC.Close() c.Assert(err, IsNil) c.Assert(result, DeepEquals, targetBuf) } func FuzzPatchDelta(f *testing.F) { f.Fuzz(func(t *testing.T, input []byte) { input_0 := input[:len(input)/2] input_1 := input[len(input)/2:] PatchDelta(input_0, input_1) }) }