github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/synchronization/rsync/engine_test.go (about)

     1  package rsync
     2  
     3  import (
     4  	"bytes"
     5  	"math/rand"
     6  	"testing"
     7  )
     8  
     9  // TestBlockHashNilInvalid verifies that a nil block hash is treated as invalid.
    10  func TestBlockHashNilInvalid(t *testing.T) {
    11  	var hash *BlockHash
    12  	if hash.EnsureValid() == nil {
    13  		t.Error("nil block hash considered valid")
    14  	}
    15  }
    16  
    17  // TestBlockHashNilStrongHashInvalid verifies that a block has with a nil strong
    18  // hash is treated as invalid.
    19  func TestBlockHashNilStrongHashInvalid(t *testing.T) {
    20  	hash := &BlockHash{Weak: 5}
    21  	if hash.EnsureValid() == nil {
    22  		t.Error("block hash with nil strong hash considered valid")
    23  	}
    24  }
    25  
    26  // TestBlockHashEmptyStrongHashInvalid verifies that a block has with an empty
    27  // strong hash is treated as invalid.
    28  func TestBlockHashEmptyStrongHashInvalid(t *testing.T) {
    29  	hash := &BlockHash{Weak: 5, Strong: make([]byte, 0)}
    30  	if hash.EnsureValid() == nil {
    31  		t.Error("block hash with empty strong hash considered valid")
    32  	}
    33  }
    34  
    35  // TestSignatureNilInvalid verifies that a nil signature is treated as invalid.
    36  func TestSignatureNilInvalid(t *testing.T) {
    37  	var signature *Signature
    38  	if signature.EnsureValid() == nil {
    39  		t.Error("nil signature considered valid")
    40  	}
    41  }
    42  
    43  // TestSignatureZeroBlockSizeNonZeroLastBlockSizeInvalid verifies the
    44  // EnsureValid behavior of Signature when the block size is zero and the last
    45  // block size is non-zero.
    46  func TestSignatureZeroBlockSizeNonZeroLastBlockSizeInvalid(t *testing.T) {
    47  	signature := &Signature{LastBlockSize: 8192}
    48  	if signature.EnsureValid() == nil {
    49  		t.Error("zero block size with non-zero last block size considered valid")
    50  	}
    51  }
    52  
    53  // TestSignatureZeroBlockSizeWithHashesInvalid verifies the EnsureValid behavior
    54  // of Signature when the block size is zero and hashes are present.
    55  func TestSignatureZeroBlockSizeWithHashesInvalid(t *testing.T) {
    56  	signature := &Signature{Hashes: []*BlockHash{{Weak: 5, Strong: []byte{0x0}}}}
    57  	if signature.EnsureValid() == nil {
    58  		t.Error("zero block size with hashes considered valid")
    59  	}
    60  }
    61  
    62  // TestSignatureZeroLastBlockSizeInvalid verifies the EnsureValid behavior of
    63  // Signature when the last block size is 0.
    64  func TestSignatureZeroLastBlockSizeInvalid(t *testing.T) {
    65  	signature := &Signature{BlockSize: 8192}
    66  	if signature.EnsureValid() == nil {
    67  		t.Error("zero last block size considered valid")
    68  	}
    69  }
    70  
    71  // TestSignatureLastBlockSizeTooBigInvalid verifies the EnsureValid behavior of
    72  // Signature when the last block size is too big.
    73  func TestSignatureLastBlockSizeTooBigInvalid(t *testing.T) {
    74  	signature := &Signature{BlockSize: 8192, LastBlockSize: 8193}
    75  	if signature.EnsureValid() == nil {
    76  		t.Error("overly large last block size considered valid")
    77  	}
    78  }
    79  
    80  // TestSignatureNoHashesInvalid verifies the EnsureValid behavior of Signature
    81  // when no hashes are present.
    82  func TestSignatureNoHashesInvalid(t *testing.T) {
    83  	signature := &Signature{BlockSize: 8192, LastBlockSize: 8192}
    84  	if signature.EnsureValid() == nil {
    85  		t.Error("signature with no hashes considered valid")
    86  	}
    87  }
    88  
    89  // TestSignatureInvalidHashesInvalid verifies the EnsureValid behavior of
    90  // Signature when invalid hashes are present.
    91  func TestSignatureInvalidHashesInvalid(t *testing.T) {
    92  	signature := &Signature{
    93  		BlockSize:     8192,
    94  		LastBlockSize: 8192,
    95  		Hashes:        []*BlockHash{nil},
    96  	}
    97  	if signature.EnsureValid() == nil {
    98  		t.Error("signature with no hashes considered valid")
    99  	}
   100  }
   101  
   102  // TestSignatureValid verifies the EnsureValid behavior of Signature for a valid
   103  // signature.
   104  func TestSignatureValid(t *testing.T) {
   105  	signature := &Signature{
   106  		BlockSize:     8192,
   107  		LastBlockSize: 8192,
   108  		Hashes:        []*BlockHash{{Weak: 1, Strong: []byte{0x0}}},
   109  	}
   110  	if err := signature.EnsureValid(); err != nil {
   111  		t.Error("valid signature failed validation:", err)
   112  	}
   113  }
   114  
   115  // TestOperationNilInvalid verifies that a nil operation is treated as invalid.
   116  func TestOperationNilInvalid(t *testing.T) {
   117  	var operation *Operation
   118  	if operation.EnsureValid() == nil {
   119  		t.Error("nil operation considered valid")
   120  	}
   121  }
   122  
   123  // TestOperationDataAndStartInvalid verifies the EnsureValid behavior of
   124  // Operation when data and a block start index are provided.
   125  func TestOperationDataAndStartInvalid(t *testing.T) {
   126  	operation := &Operation{Data: []byte{0}, Start: 4}
   127  	if operation.EnsureValid() == nil {
   128  		t.Error("operation with data and start considered valid")
   129  	}
   130  }
   131  
   132  // TestOperationDataAndCountInvalid verifies the EnsureValid behavior of
   133  // Operation when data and a block count are provided.
   134  func TestOperationDataAndCountInvalid(t *testing.T) {
   135  	operation := &Operation{Data: []byte{0}, Count: 4}
   136  	if operation.EnsureValid() == nil {
   137  		t.Error("operation with data and count considered valid")
   138  	}
   139  }
   140  
   141  // TestOperationZeroCountInvalid verifies the EnsureValid behavior of Operation
   142  // when the block count is zero.
   143  func TestOperationZeroCountInvalid(t *testing.T) {
   144  	operation := &Operation{Start: 40}
   145  	if operation.EnsureValid() == nil {
   146  		t.Error("operation with zero count considered valid")
   147  	}
   148  }
   149  
   150  // TestOperationDataValid verifies the EnsureValid behavior of Operation in the
   151  // case of a valid data operation.
   152  func TestOperationDataValid(t *testing.T) {
   153  	operation := &Operation{Data: []byte{0}}
   154  	if err := operation.EnsureValid(); err != nil {
   155  		t.Error("valid data operation considered invalid")
   156  	}
   157  }
   158  
   159  // TestOperationBlocksValid verifies the EnsureValid behavior of Operation in the
   160  // case of a valid block operation.
   161  func TestOperationBlocksValid(t *testing.T) {
   162  	operation := &Operation{Start: 10, Count: 50}
   163  	if err := operation.EnsureValid(); err != nil {
   164  		t.Error("valid block operation considered invalid")
   165  	}
   166  }
   167  
   168  // TestMinimumBlockSize verifies that OptimalBlockSizeForBaseLength returns a
   169  // sane minimum block size.
   170  func TestMinimumBlockSize(t *testing.T) {
   171  	if s := OptimalBlockSizeForBaseLength(1); s != minimumOptimalBlockSize {
   172  		t.Error("incorrect minimum block size:", s, "!=", minimumOptimalBlockSize)
   173  	}
   174  }
   175  
   176  // TestMaximumBlockSize verifies that OptimalBlockSizeForBaseLength returns a
   177  // sane maximum block size.
   178  func TestMaximumBlockSize(t *testing.T) {
   179  	if s := OptimalBlockSizeForBaseLength(maximumOptimalBlockSize * maximumOptimalBlockSize); s != maximumOptimalBlockSize {
   180  		t.Error("incorrect maximum block size:", s, "!=", maximumOptimalBlockSize)
   181  	}
   182  }
   183  
   184  // TestOptimalBlockSizeForBase verifies the behavior of OptimalBlockSizeForBase.
   185  func TestOptimalBlockSizeForBase(t *testing.T) {
   186  	// Create a base.
   187  	baseLength := uint64(1234567)
   188  	base := bytes.NewReader(make([]byte, baseLength))
   189  
   190  	// Compute the optimal block size using OptimalBlockSizeForBase.
   191  	optimalBlockSize, err := OptimalBlockSizeForBase(base)
   192  	if err != nil {
   193  		t.Fatal("unable to compute optimal block size for base")
   194  	}
   195  
   196  	// Compate it with what we'd expect by computing manually.
   197  	expectedOptimalBlockSize := OptimalBlockSizeForBaseLength(baseLength)
   198  	if optimalBlockSize != expectedOptimalBlockSize {
   199  		t.Error(
   200  			"mismatch between optimal block size computations:",
   201  			optimalBlockSize, "!=", expectedOptimalBlockSize,
   202  		)
   203  	}
   204  
   205  	// Ensure that the reader was reset to the beginning.
   206  	if uint64(base.Len()) != baseLength {
   207  		t.Error("base was not reset to beginning")
   208  	}
   209  }
   210  
   211  // testDataGenerator generates repeatable random byte sequences with optional
   212  // mutations and data prepending.
   213  type testDataGenerator struct {
   214  	length    int
   215  	seed      int64
   216  	mutations []int
   217  	prepend   []byte
   218  }
   219  
   220  // generate creates a byte sequence based on the generator's parameters.
   221  func (g testDataGenerator) generate() []byte {
   222  	// Create a random number generator.
   223  	random := rand.New(rand.NewSource(g.seed))
   224  
   225  	// Create a buffer and fill it. The read is guaranteed to succeed.
   226  	result := make([]byte, g.length)
   227  	random.Read(result)
   228  
   229  	// Mutate.
   230  	for _, index := range g.mutations {
   231  		result[index]++
   232  	}
   233  
   234  	// Prepend data if necessary. This isn't super-efficient, but it's fine for
   235  	// testing.
   236  	if len(g.prepend) > 0 {
   237  		result = append(g.prepend, result...)
   238  	}
   239  
   240  	// Done.
   241  	return result
   242  }
   243  
   244  // engineTestCase performs an rsync cycle with a specified base and target and
   245  // verifies certain behavior/parameters of the cycle.
   246  type engineTestCase struct {
   247  	base                      testDataGenerator
   248  	target                    testDataGenerator
   249  	blockSize                 uint64
   250  	maxDataOpSize             uint64
   251  	numberOfOperations        uint
   252  	numberOfDataOperations    uint
   253  	expectCoalescedOperations bool
   254  }
   255  
   256  // run executes the test case.
   257  func (c engineTestCase) run(t *testing.T) {
   258  	// Mark this as a helper function.
   259  	t.Helper()
   260  
   261  	// Generate base and target data.
   262  	base := c.base.generate()
   263  	target := c.target.generate()
   264  
   265  	// Create an engine.
   266  	engine := NewEngine()
   267  
   268  	// Compute the base signature. Verify that it's sane and that it used the
   269  	// correct block size.
   270  	signature := engine.BytesSignature(base, c.blockSize)
   271  	if err := signature.EnsureValid(); err != nil {
   272  		t.Fatal("generated signature was invalid:", err)
   273  	} else if len(signature.Hashes) != 0 {
   274  		if c.blockSize != 0 && signature.BlockSize != c.blockSize {
   275  			t.Error(
   276  				"generated signature did not have correct block size:",
   277  				signature.BlockSize, "!=", c.blockSize,
   278  			)
   279  		}
   280  	}
   281  
   282  	// Compute a delta.
   283  	delta := engine.DeltifyBytes(target, signature, c.maxDataOpSize)
   284  
   285  	// Determine what we should expect for the maximumd data operation size.
   286  	expectedMaxDataOpSize := c.maxDataOpSize
   287  	if expectedMaxDataOpSize == 0 {
   288  		expectedMaxDataOpSize = DefaultMaximumDataOperationSize
   289  	}
   290  
   291  	// Validate the delta and verify its statistics.
   292  	nDataOperations := uint(0)
   293  	haveCoalescedOperations := false
   294  	for _, o := range delta {
   295  		if err := o.EnsureValid(); err != nil {
   296  			t.Error("invalid operation:", err)
   297  		} else if dataLength := uint64(len(o.Data)); dataLength > 0 {
   298  			if dataLength > expectedMaxDataOpSize {
   299  				t.Error(
   300  					"data operation size greater than allowed:",
   301  					dataLength, ">", expectedMaxDataOpSize,
   302  				)
   303  			}
   304  			nDataOperations++
   305  		} else if o.Count > 1 {
   306  			haveCoalescedOperations = true
   307  		}
   308  	}
   309  	if uint(len(delta)) != c.numberOfOperations {
   310  		t.Error(
   311  			"observed different number of operations than expected:",
   312  			len(delta), "!=", c.numberOfOperations,
   313  		)
   314  	}
   315  	if nDataOperations != c.numberOfDataOperations {
   316  		t.Error(
   317  			"observed different number of data operations than expected:",
   318  			nDataOperations, ">", c.numberOfDataOperations,
   319  		)
   320  	}
   321  	if haveCoalescedOperations != c.expectCoalescedOperations {
   322  		t.Error(
   323  			"expectations about coalescing not met:",
   324  			haveCoalescedOperations, "!=", c.expectCoalescedOperations,
   325  		)
   326  	}
   327  
   328  	// Apply the delta.
   329  	patched, err := engine.PatchBytes(base, signature, delta)
   330  	if err != nil {
   331  		t.Fatal("unable to patch bytes:", err)
   332  	}
   333  
   334  	// Verify success.
   335  	if !bytes.Equal(patched, target) {
   336  		t.Error("patched data did not match expected")
   337  	}
   338  }
   339  
   340  // TestBothEmpty verifies that no operations are exchanged in the case that both
   341  // base and target are empty.
   342  func TestBothEmpty(t *testing.T) {
   343  	test := engineTestCase{
   344  		base:   testDataGenerator{},
   345  		target: testDataGenerator{},
   346  	}
   347  	test.run(t)
   348  }
   349  
   350  // TestBaseEmptyMaxDataOperationMultiple verifies that data sent against an
   351  // empty base will just be transmitted as data operations, and verifies that
   352  // this is done correctly in the case that the data length is a multiple of the
   353  // maximum data operation size.
   354  func TestBaseEmptyMaxDataOperationMultiple(t *testing.T) {
   355  	test := engineTestCase{
   356  		base:                   testDataGenerator{},
   357  		target:                 testDataGenerator{10240, 473, nil, nil},
   358  		maxDataOpSize:          1024,
   359  		numberOfOperations:     10,
   360  		numberOfDataOperations: 10,
   361  	}
   362  	test.run(t)
   363  }
   364  
   365  // TestBaseEmptyNonMaxDataOperationMultiple verifies that data sent against an
   366  // empty base will just be transmitted as data operations, and verifies that
   367  // this is done correctly in the case that one operation that is less than the
   368  // maximum data operation size.
   369  func TestBaseEmptyNonMaxDataOperationMultiple(t *testing.T) {
   370  	test := engineTestCase{
   371  		base:                   testDataGenerator{},
   372  		target:                 testDataGenerator{10241, 473, nil, nil},
   373  		maxDataOpSize:          1024,
   374  		numberOfOperations:     11,
   375  		numberOfDataOperations: 11,
   376  	}
   377  	test.run(t)
   378  }
   379  
   380  // TestTargetEmpty verifies that a completely empty target can be transmitted
   381  // without any operations.
   382  func TestTargetEmpty(t *testing.T) {
   383  	test := engineTestCase{
   384  		base:   testDataGenerator{12345, 473, nil, nil},
   385  		target: testDataGenerator{},
   386  	}
   387  	test.run(t)
   388  }
   389  
   390  // TestSame verifies that completely equivalent data will be sent in a single
   391  // coalesced block operation. It requires that the data length be at least two
   392  // blocks in length (although one may be a short block) so that coalescing can
   393  // occur.
   394  func TestSame(t *testing.T) {
   395  	test := engineTestCase{
   396  		base:                      testDataGenerator{1234567, 473, nil, nil},
   397  		target:                    testDataGenerator{1234567, 473, nil, nil},
   398  		numberOfOperations:        1,
   399  		expectCoalescedOperations: true,
   400  	}
   401  	test.run(t)
   402  }
   403  
   404  // TestSame1Mutation verifies that data which is identical except for a single
   405  // mutation in the second block (of ten blocks) will be transmitted as two
   406  // block operations (one of which is coalesced) and a single data operation. It
   407  // sets the maximum data operation size to ensure that the mutated block can be
   408  // sent in a single data operation.
   409  func TestSame1Mutation(t *testing.T) {
   410  	test := engineTestCase{
   411  		base:                      testDataGenerator{10240, 473, nil, nil},
   412  		target:                    testDataGenerator{10240, 473, []int{1300}, nil},
   413  		blockSize:                 1024,
   414  		maxDataOpSize:             1024,
   415  		numberOfOperations:        3,
   416  		numberOfDataOperations:    1,
   417  		expectCoalescedOperations: true,
   418  	}
   419  	test.run(t)
   420  }
   421  
   422  // TestSame2Mutations verifies that data which is identical except for mutations
   423  // in the second and fourth blocks (of five blocks, the last of which is short)
   424  // will be transmitted as three block operations (none of which are coalesced)
   425  // and two data operations. It sets the maximum data operation size to ensure
   426  // that the mutated blocks can be sent in single data operations.
   427  func TestSame2Mutations(t *testing.T) {
   428  	test := engineTestCase{
   429  		base:                   testDataGenerator{10220, 473, nil, nil},
   430  		target:                 testDataGenerator{10220, 473, []int{2073, 7000}, nil},
   431  		blockSize:              2048,
   432  		maxDataOpSize:          2048,
   433  		numberOfOperations:     5,
   434  		numberOfDataOperations: 2,
   435  	}
   436  	test.run(t)
   437  }
   438  
   439  // TestTruncateOnBlockBoundary verifies that truncation on a block boundary will
   440  // send only a single coalesced block operation when data is truncated on the
   441  // block boundary.
   442  func TestTruncateOnBlockBoundary(t *testing.T) {
   443  	test := engineTestCase{
   444  		base:                      testDataGenerator{999, 212, nil, nil},
   445  		target:                    testDataGenerator{666, 212, nil, nil},
   446  		blockSize:                 333,
   447  		numberOfOperations:        1,
   448  		expectCoalescedOperations: true,
   449  	}
   450  	test.run(t)
   451  }
   452  
   453  // TestTruncateOffBlockBoundary verifies that truncation that's not on a block
   454  // boundary will send only a single coalesced block operation and a single data
   455  // operation when data is truncated within one maximum data operation size of a
   456  // block boundary.
   457  func TestTruncateOffBlockBoundary(t *testing.T) {
   458  	test := engineTestCase{
   459  		base:                      testDataGenerator{888, 912, nil, nil},
   460  		target:                    testDataGenerator{790, 912, nil, nil},
   461  		blockSize:                 111,
   462  		maxDataOpSize:             1024,
   463  		numberOfOperations:        2,
   464  		numberOfDataOperations:    1,
   465  		expectCoalescedOperations: true,
   466  	}
   467  	test.run(t)
   468  }
   469  
   470  // TestPrepend verifies that data which has been prepended with data shorter
   471  // than the maximum data operation size can be transmitted in a single data
   472  // operation and a single coalesced block operation. It also tests short block
   473  // matching.
   474  func TestPrepend(t *testing.T) {
   475  	test := engineTestCase{
   476  		base:                      testDataGenerator{9880, 11, nil, nil},
   477  		target:                    testDataGenerator{9880, 11, nil, []byte{1, 2, 3}},
   478  		blockSize:                 1234,
   479  		maxDataOpSize:             5,
   480  		numberOfOperations:        2,
   481  		numberOfDataOperations:    1,
   482  		expectCoalescedOperations: true,
   483  	}
   484  	test.run(t)
   485  }
   486  
   487  // TestAppend verifies that data which has been appended with data shorter than
   488  // the maximum data operation size can be transmitted in a single coalesced
   489  // block operation and a data operation. Because the rsync algorithm can't match
   490  // short blocks that aren't at the end of the target, we have to ensure that the
   491  // short block and the appended data can fit into a single data operation.
   492  func TestAppend(t *testing.T) {
   493  	test := engineTestCase{
   494  		base:                      testDataGenerator{45271, 473, nil, nil},
   495  		target:                    testDataGenerator{45271 + 876, 473, nil, nil},
   496  		blockSize:                 6453,
   497  		maxDataOpSize:             1024,
   498  		numberOfOperations:        2,
   499  		numberOfDataOperations:    1,
   500  		expectCoalescedOperations: true,
   501  	}
   502  	test.run(t)
   503  }
   504  
   505  // TestDifferentDataSameLength verifies that different data with no matching
   506  // blocks but the same length won't be influenced by the matching length and
   507  // will just send the new data.
   508  func TestDifferentDataSameLength(t *testing.T) {
   509  	test := engineTestCase{
   510  		base:                   testDataGenerator{10473, 473, nil, nil},
   511  		target:                 testDataGenerator{10473, 182, nil, nil},
   512  		maxDataOpSize:          1024,
   513  		numberOfOperations:     11,
   514  		numberOfDataOperations: 11,
   515  	}
   516  	test.run(t)
   517  }
   518  
   519  // TestDifferentDataDifferentLength verifies that different data with no
   520  // matching blocks and different total length will just send the new data.
   521  func TestDifferentDataDifferentLength(t *testing.T) {
   522  	test := engineTestCase{
   523  		base:                   testDataGenerator{678345, 473, nil, nil},
   524  		target:                 testDataGenerator{473711, 182, nil, nil},
   525  		maxDataOpSize:          12304,
   526  		numberOfOperations:     39,
   527  		numberOfDataOperations: 39,
   528  	}
   529  	test.run(t)
   530  }