api_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package rsync
  3. import (
  4. "bytes"
  5. "encoding/hex"
  6. "fmt"
  7. "io"
  8. "strconv"
  9. "strings"
  10. "testing"
  11. "github.com/google/go-cmp/cmp"
  12. "golang.org/x/exp/slices"
  13. "kitty/tools/utils"
  14. )
  15. var _ = fmt.Print
  16. var _ = cmp.Diff
  17. func run_roundtrip_test(t *testing.T, src_data, changed []byte, num_of_patches, total_patch_size int) {
  18. using_serialization := false
  19. t.Helper()
  20. prefix_msg := func() string {
  21. q := utils.IfElse(using_serialization, "with", "without")
  22. return fmt.Sprintf("Running %s serialization: src size: %d changed size: %d difference: %d\n",
  23. q, len(src_data), len(changed), len(changed)-len(src_data))
  24. }
  25. test_equal := func(src_data, output []byte) {
  26. if !bytes.Equal(src_data, output) {
  27. first_diff := min(len(src_data), len(output))
  28. for i := 0; i < first_diff; i++ {
  29. if src_data[i] != output[i] {
  30. first_diff = i
  31. break
  32. }
  33. }
  34. t.Fatalf("%sPatching failed: %d extra_bytes first different byte at: %d\nsrc:\n%s\nchanged:\n%s\noutput:\n%s\n",
  35. prefix_msg(), len(output)-len(src_data), first_diff, string(src_data), string(changed), string(output))
  36. }
  37. }
  38. // first try just the engine without serialization
  39. p := NewPatcher(int64(len(src_data)))
  40. signature := make([]BlockHash, 0, 128)
  41. s_it := p.rsync.CreateSignatureIterator(bytes.NewReader(changed))
  42. for {
  43. s, err := s_it()
  44. if err == nil {
  45. signature = append(signature, s)
  46. } else if err == io.EOF {
  47. break
  48. } else {
  49. t.Fatal(err)
  50. }
  51. }
  52. total_data_in_delta := 0
  53. apply_delta := func(signature []BlockHash) []byte {
  54. delta_ops, err := p.rsync.CreateDelta(bytes.NewReader(src_data), signature)
  55. if err != nil {
  56. t.Fatal(err)
  57. }
  58. if delta_ops[len(delta_ops)-1].Type != OpHash {
  59. t.Fatalf("Last operation was not OpHash")
  60. }
  61. total_data_in_delta = 0
  62. outputbuf := bytes.Buffer{}
  63. for _, op := range delta_ops {
  64. if op.Type == OpData {
  65. total_data_in_delta += len(op.Data)
  66. }
  67. p.rsync.ApplyDelta(&outputbuf, bytes.NewReader(changed), op)
  68. }
  69. return outputbuf.Bytes()
  70. }
  71. test_equal(src_data, apply_delta(signature))
  72. limit := 2 * (p.rsync.BlockSize * num_of_patches)
  73. if limit > -1 && total_data_in_delta > limit {
  74. t.Fatalf("%sUnexpectedly poor delta performance: total_patch_size: %d total_delta_size: %d limit: %d", prefix_msg(), total_patch_size, total_data_in_delta, limit)
  75. }
  76. // Now try with serialization
  77. using_serialization = true
  78. p = NewPatcher(int64(len(changed)))
  79. signature_of_changed := bytes.Buffer{}
  80. ss_it := p.CreateSignatureIterator(bytes.NewReader(changed), &signature_of_changed)
  81. var err error
  82. for {
  83. err = ss_it()
  84. if err == io.EOF {
  85. break
  86. } else if err != nil {
  87. t.Fatal(err)
  88. }
  89. }
  90. d := NewDiffer()
  91. if err := d.AddSignatureData(signature_of_changed.Bytes()); err != nil {
  92. t.Fatal(err)
  93. }
  94. db := bytes.Buffer{}
  95. it := d.CreateDelta(bytes.NewBuffer(src_data), &db)
  96. for {
  97. if err := it(); err != nil {
  98. if err == io.EOF {
  99. break
  100. }
  101. t.Fatal(err)
  102. }
  103. }
  104. deltabuf := db.Bytes()
  105. outputbuf := bytes.Buffer{}
  106. p.StartDelta(&outputbuf, bytes.NewReader(changed))
  107. for len(deltabuf) > 0 {
  108. n := min(123, len(deltabuf))
  109. if err := p.UpdateDelta(deltabuf[:n]); err != nil {
  110. t.Fatal(err)
  111. }
  112. deltabuf = deltabuf[n:]
  113. }
  114. if err := p.FinishDelta(); err != nil {
  115. t.Fatal(err)
  116. }
  117. test_equal(src_data, outputbuf.Bytes())
  118. if limit > -1 && p.total_data_in_delta > limit {
  119. t.Fatalf("%sUnexpectedly poor delta performance: total_patch_size: %d total_delta_size: %d limit: %d", prefix_msg(), total_patch_size, p.total_data_in_delta, limit)
  120. }
  121. }
  122. func generate_data(block_size, num_of_blocks int, extra ...string) []byte {
  123. e := strings.Join(extra, "")
  124. ans := make([]byte, num_of_blocks*block_size+len(e))
  125. utils.Memset(ans, '_')
  126. for i := 0; i < num_of_blocks; i++ {
  127. offset := i * block_size
  128. copy(ans[offset:], strconv.Itoa(i))
  129. }
  130. copy(ans[num_of_blocks*block_size:], e)
  131. return ans
  132. }
  133. func patch_data(data []byte, patches ...string) (num_of_patches, total_patch_size int) {
  134. num_of_patches = len(patches)
  135. for _, patch := range patches {
  136. o, r, _ := strings.Cut(patch, ":")
  137. total_patch_size += len(r)
  138. if offset, err := strconv.Atoi(o); err == nil {
  139. copy(data[offset:], r)
  140. } else {
  141. panic(err)
  142. }
  143. }
  144. return
  145. }
  146. func TestRsyncRoundtrip(t *testing.T) {
  147. block_size := 16
  148. src_data := generate_data(block_size, 16)
  149. changed := slices.Clone(src_data)
  150. num_of_patches, total_patch_size := patch_data(changed, "3:patch1", "16:patch2", "130:ptch3", "176:patch4", "222:XXYY")
  151. run_roundtrip_test(t, src_data, src_data[block_size:], 1, block_size)
  152. run_roundtrip_test(t, src_data, changed, num_of_patches, total_patch_size)
  153. run_roundtrip_test(t, src_data, []byte{}, -1, 0)
  154. run_roundtrip_test(t, src_data, src_data, 0, 0)
  155. run_roundtrip_test(t, src_data, changed[:len(changed)-3], num_of_patches, total_patch_size)
  156. run_roundtrip_test(t, src_data, append(changed[:37], changed[81:]...), num_of_patches, total_patch_size)
  157. block_size = 13
  158. src_data = generate_data(block_size, 17, "trailer")
  159. changed = slices.Clone(src_data)
  160. num_of_patches, total_patch_size = patch_data(changed, "0:patch1", "19:patch2")
  161. run_roundtrip_test(t, src_data, changed, num_of_patches, total_patch_size)
  162. run_roundtrip_test(t, src_data, changed[:len(changed)-3], num_of_patches, total_patch_size)
  163. run_roundtrip_test(t, src_data, append(changed, "xyz..."...), num_of_patches, total_patch_size)
  164. }
  165. func TestRsyncHashers(t *testing.T) {
  166. h := new_xxh3_64()
  167. h.Write([]byte("abcd"))
  168. if diff := cmp.Diff(hex.EncodeToString(h.Sum(nil)), `6497a96f53a89890`); diff != "" {
  169. t.Fatalf(diff)
  170. }
  171. if diff := cmp.Diff(h.Sum64(), uint64(7248448420886124688)); diff != "" {
  172. t.Fatalf(diff)
  173. }
  174. h2 := new_xxh3_128()
  175. h2.Write([]byte("abcd"))
  176. if diff := cmp.Diff(hex.EncodeToString(h2.Sum(nil)), `8d6b60383dfa90c21be79eecd1b1353d`); diff != "" {
  177. t.Fatalf(diff)
  178. }
  179. }