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 }