delta_diff_utils_unittest.cc revision 2d3b2d635e50c6886e285afb86c3187d9e0bd360
1// Copyright 2015 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "update_engine/payload_generator/delta_diff_utils.h" 6 7#include <algorithm> 8#include <string> 9#include <vector> 10 11#include <base/files/scoped_file.h> 12#include <base/format_macros.h> 13#include <base/strings/stringprintf.h> 14#include <gtest/gtest.h> 15 16#include "update_engine/payload_generator/delta_diff_generator.h" 17#include "update_engine/payload_generator/extent_ranges.h" 18#include "update_engine/payload_generator/extent_utils.h" 19#include "update_engine/payload_generator/fake_filesystem.h" 20#include "update_engine/test_utils.h" 21#include "update_engine/utils.h" 22 23using std::string; 24using std::vector; 25 26namespace chromeos_update_engine { 27 28namespace { 29 30// Writes the |data| in the blocks specified by |extents| on the partition 31// |part_path|. The |data| size could be smaller than the size of the blocks 32// passed. 33bool WriteExtents(const string& part_path, 34 const vector<Extent>& extents, 35 off_t block_size, 36 const chromeos::Blob& data) { 37 uint64_t offset = 0; 38 base::ScopedFILE fp(fopen(part_path.c_str(), "r+")); 39 TEST_AND_RETURN_FALSE(fp.get()); 40 41 for (const Extent& extent : extents) { 42 if (offset >= data.size()) 43 break; 44 TEST_AND_RETURN_FALSE( 45 fseek(fp.get(), extent.start_block() * block_size, SEEK_SET) == 0); 46 uint64_t to_write = std::min(extent.num_blocks() * block_size, 47 data.size() - offset); 48 TEST_AND_RETURN_FALSE( 49 fwrite(data.data() + offset, 1, to_write, fp.get()) == to_write); 50 offset += extent.num_blocks() * block_size; 51 } 52 return true; 53} 54 55// Create a fake filesystem of the given |size| and initialize the partition 56// holding it in the PartitionConfig |part|. 57void CreatePartition(PartitionConfig* part, const string& pattern, 58 uint64_t block_size, off_t size) { 59 int fd = -1; 60 ASSERT_TRUE(utils::MakeTempFile(pattern.c_str(), &part->path, &fd)); 61 ASSERT_EQ(0, ftruncate(fd, size)); 62 ASSERT_EQ(0, close(fd)); 63 part->fs_interface.reset(new FakeFilesystem(block_size, size / block_size)); 64 part->size = size; 65} 66 67// Writes to the |partition| path blocks such they are all different and they 68// include the tag passed in |tag|, making them also different to any other 69// block on a partition initialized with this function with a different tag. 70// The |block_size| should be a divisor of the partition size. 71// Returns whether the function succeeded writing the partition. 72bool InitializePartitionWithUniqueBlocks(const PartitionConfig& part, 73 uint64_t block_size, 74 uint64_t tag) { 75 TEST_AND_RETURN_FALSE(part.size % block_size == 0); 76 size_t num_blocks = part.size / block_size; 77 chromeos::Blob file_data(part.size); 78 for (size_t i = 0; i < num_blocks; ++i) { 79 string prefix = base::StringPrintf( 80 "block tag 0x%.16" PRIx64 ", block number %16" PRIuS " ", tag, i); 81 chromeos::Blob block_data(prefix.begin(), prefix.end()); 82 TEST_AND_RETURN_FALSE(prefix.size() <= block_size); 83 block_data.resize(block_size, 'X'); 84 std::copy(block_data.begin(), block_data.end(), 85 file_data.begin() + i * block_size); 86 } 87 return test_utils::WriteFileVector(part.path, file_data); 88} 89 90} // namespace 91 92class DeltaDiffUtilsTest : public ::testing::Test { 93 protected: 94 const uint64_t kDefaultBlockCount = 128; 95 96 void SetUp() override { 97 CreatePartition(&old_part_, "DeltaDiffUtilsTest-old_part-XXXXXX", 98 block_size_, block_size_ * kDefaultBlockCount); 99 CreatePartition(&new_part_, "DeltaDiffUtilsTest-old_part-XXXXXX", 100 block_size_, block_size_ * kDefaultBlockCount); 101 ASSERT_TRUE(utils::MakeTempFile("DeltaDiffUtilsTest-blob-XXXXXX", 102 &blob_path_, 103 &blob_fd_)); 104 } 105 106 void TearDown() override { 107 unlink(old_part_.path.c_str()); 108 unlink(new_part_.path.c_str()); 109 if (blob_fd_ != -1) 110 close(blob_fd_); 111 unlink(blob_path_.c_str()); 112 } 113 114 // Helper function to call DeltaMovedAndZeroBlocks() using this class' data 115 // members. This simply avoid repeating all the arguments that never change. 116 bool RunDeltaMovedAndZeroBlocks(ssize_t chunk_blocks, 117 bool src_ops_allowed) { 118 return diff_utils::DeltaMovedAndZeroBlocks( 119 &aops_, 120 old_part_.path, 121 new_part_.path, 122 old_part_.size / block_size_, 123 new_part_.size / block_size_, 124 chunk_blocks, 125 src_ops_allowed, 126 blob_fd_, 127 &blob_size_, 128 &old_visited_blocks_, 129 &new_visited_blocks_); 130 } 131 132 // Old and new temporary partitions used in the tests. These are initialized 133 // with 134 PartitionConfig old_part_{PartitionName::kRootfs}; 135 PartitionConfig new_part_{PartitionName::kRootfs}; 136 137 // The file holding the output blob from the various diff utils functions. 138 string blob_path_; 139 int blob_fd_{-1}; 140 off_t blob_size_{0}; 141 142 size_t block_size_{kBlockSize}; 143 144 // Default input/output arguments used when calling DeltaMovedAndZeroBlocks(). 145 vector<AnnotatedOperation> aops_; 146 ExtentRanges old_visited_blocks_; 147 ExtentRanges new_visited_blocks_; 148}; 149 150TEST_F(DeltaDiffUtilsTest, MoveSmallTest) { 151 chromeos::Blob data_blob(block_size_); 152 test_utils::FillWithData(&data_blob); 153 154 // The old file is on a different block than the new one. 155 vector<Extent> old_extents = { ExtentForRange(11, 1) }; 156 vector<Extent> new_extents = { ExtentForRange(1, 1) }; 157 158 EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob)); 159 EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob)); 160 161 chromeos::Blob data; 162 DeltaArchiveManifest_InstallOperation op; 163 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 164 old_part_.path, 165 new_part_.path, 166 old_extents, 167 new_extents, 168 true, // bsdiff_allowed 169 &data, 170 &op, 171 false)); // src_ops_allowed 172 EXPECT_TRUE(data.empty()); 173 174 EXPECT_TRUE(op.has_type()); 175 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type()); 176 EXPECT_FALSE(op.has_data_offset()); 177 EXPECT_FALSE(op.has_data_length()); 178 EXPECT_EQ(1, op.src_extents_size()); 179 EXPECT_EQ(kBlockSize, op.src_length()); 180 EXPECT_EQ(1, op.dst_extents_size()); 181 EXPECT_EQ(kBlockSize, op.dst_length()); 182 EXPECT_EQ(BlocksInExtents(op.src_extents()), 183 BlocksInExtents(op.dst_extents())); 184 EXPECT_EQ(1, BlocksInExtents(op.dst_extents())); 185} 186 187TEST_F(DeltaDiffUtilsTest, MoveWithSameBlock) { 188 // Setup the old/new files so that it has immobile chunks; we make sure to 189 // utilize all sub-cases of such chunks: blocks 21--22 induce a split (src) 190 // and complete removal (dst), whereas blocks 24--25 induce trimming of the 191 // tail (src) and head (dst) of extents. The final block (29) is used for 192 // ensuring we properly account for the number of bytes removed in cases where 193 // the last block is partly filled. The detailed configuration: 194 // 195 // Old: [ 20 21 22 23 24 25 ] [ 28 29 ] 196 // New: [ 18 ] [ 21 22 ] [ 20 ] [ 24 25 26 ] [ 29 ] 197 // Same: ^^ ^^ ^^ ^^ ^^ 198 vector<Extent> old_extents = { 199 ExtentForRange(20, 6), 200 ExtentForRange(28, 2) }; 201 vector<Extent> new_extents = { 202 ExtentForRange(18, 1), 203 ExtentForRange(21, 2), 204 ExtentForRange(20, 1), 205 ExtentForRange(24, 3), 206 ExtentForRange(29, 1) }; 207 208 uint64_t num_blocks = BlocksInExtents(old_extents); 209 EXPECT_EQ(num_blocks, BlocksInExtents(new_extents)); 210 211 // The size of the data should match the total number of blocks. Each block 212 // has a different content. 213 chromeos::Blob file_data; 214 for (uint64_t i = 0; i < num_blocks; ++i) { 215 file_data.resize(file_data.size() + kBlockSize, 'a' + i); 216 } 217 218 EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, file_data)); 219 EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, file_data)); 220 221 chromeos::Blob data; 222 DeltaArchiveManifest_InstallOperation op; 223 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 224 old_part_.path, 225 new_part_.path, 226 old_extents, 227 new_extents, 228 true, // bsdiff_allowed 229 &data, 230 &op, 231 false)); // src_ops_allowed 232 233 EXPECT_TRUE(data.empty()); 234 235 EXPECT_TRUE(op.has_type()); 236 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type()); 237 EXPECT_FALSE(op.has_data_offset()); 238 EXPECT_FALSE(op.has_data_length()); 239 240 // The expected old and new extents that actually moved. See comment above. 241 old_extents = { 242 ExtentForRange(20, 1), 243 ExtentForRange(23, 1), 244 ExtentForRange(28, 1) }; 245 new_extents = { 246 ExtentForRange(18, 1), 247 ExtentForRange(20, 1), 248 ExtentForRange(26, 1) }; 249 num_blocks = BlocksInExtents(old_extents); 250 251 EXPECT_EQ(num_blocks * kBlockSize, op.src_length()); 252 EXPECT_EQ(num_blocks * kBlockSize, op.dst_length()); 253 254 EXPECT_EQ(old_extents.size(), op.src_extents_size()); 255 for (int i = 0; i < op.src_extents_size(); i++) { 256 EXPECT_EQ(old_extents[i].start_block(), op.src_extents(i).start_block()) 257 << "i == " << i; 258 EXPECT_EQ(old_extents[i].num_blocks(), op.src_extents(i).num_blocks()) 259 << "i == " << i; 260 } 261 262 EXPECT_EQ(new_extents.size(), op.dst_extents_size()); 263 for (int i = 0; i < op.dst_extents_size(); i++) { 264 EXPECT_EQ(new_extents[i].start_block(), op.dst_extents(i).start_block()) 265 << "i == " << i; 266 EXPECT_EQ(new_extents[i].num_blocks(), op.dst_extents(i).num_blocks()) 267 << "i == " << i; 268 } 269} 270 271TEST_F(DeltaDiffUtilsTest, BsdiffSmallTest) { 272 // Test a BSDIFF operation from block 1 to block 2. 273 chromeos::Blob data_blob(kBlockSize); 274 test_utils::FillWithData(&data_blob); 275 276 // The old file is on a different block than the new one. 277 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 278 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 279 280 EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob)); 281 // Modify one byte in the new file. 282 data_blob[0]++; 283 EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob)); 284 285 chromeos::Blob data; 286 DeltaArchiveManifest_InstallOperation op; 287 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 288 old_part_.path, 289 new_part_.path, 290 old_extents, 291 new_extents, 292 true, // bsdiff_allowed 293 &data, 294 &op, 295 false)); // src_ops_allowed 296 297 EXPECT_FALSE(data.empty()); 298 299 EXPECT_TRUE(op.has_type()); 300 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_BSDIFF, op.type()); 301 EXPECT_FALSE(op.has_data_offset()); 302 EXPECT_FALSE(op.has_data_length()); 303 EXPECT_EQ(1, op.src_extents_size()); 304 EXPECT_EQ(kBlockSize, op.src_length()); 305 EXPECT_EQ(1, op.dst_extents_size()); 306 EXPECT_EQ(kBlockSize, op.dst_length()); 307 EXPECT_EQ(BlocksInExtents(op.src_extents()), 308 BlocksInExtents(op.dst_extents())); 309 EXPECT_EQ(1, BlocksInExtents(op.dst_extents())); 310} 311 312TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedTest) { 313 // Same setup as the previous test, but this time BSDIFF operations are not 314 // allowed. 315 chromeos::Blob data_blob(kBlockSize); 316 test_utils::FillWithData(&data_blob); 317 318 // The old file is on a different block than the new one. 319 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 320 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 321 322 EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob)); 323 // Modify one byte in the new file. 324 data_blob[0]++; 325 EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob)); 326 327 chromeos::Blob data; 328 DeltaArchiveManifest_InstallOperation op; 329 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 330 old_part_.path, 331 new_part_.path, 332 old_extents, 333 new_extents, 334 false, // bsdiff_allowed 335 &data, 336 &op, 337 false)); // src_ops_allowed 338 339 EXPECT_FALSE(data.empty()); 340 341 // The point of this test is that we don't use BSDIFF the way the above 342 // did. The rest of the details are to be caught in other tests. 343 EXPECT_TRUE(op.has_type()); 344 EXPECT_NE(DeltaArchiveManifest_InstallOperation_Type_BSDIFF, op.type()); 345} 346 347TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedMoveTest) { 348 chromeos::Blob data_blob(kBlockSize); 349 test_utils::FillWithData(&data_blob); 350 351 // The old file is on a different block than the new one. 352 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 353 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 354 355 EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob)); 356 EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob)); 357 358 chromeos::Blob data; 359 DeltaArchiveManifest_InstallOperation op; 360 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 361 old_part_.path, 362 new_part_.path, 363 old_extents, 364 new_extents, 365 false, // bsdiff_allowed 366 &data, 367 &op, 368 false)); // src_ops_allowed 369 370 EXPECT_TRUE(data.empty()); 371 372 // The point of this test is that we can still use a MOVE for a file 373 // that is blacklisted. 374 EXPECT_TRUE(op.has_type()); 375 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type()); 376} 377 378TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) { 379 // The old file is on a different block than the new one. 380 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 381 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 382 383 // Make a blob that's just 1's that will compress well. 384 chromeos::Blob ones(kBlockSize, 1); 385 386 // Make a blob with random data that won't compress well. 387 chromeos::Blob random_data; 388 std::mt19937 gen(12345); 389 std::uniform_int_distribution<uint8_t> dis(0, 255); 390 for (uint32_t i = 0; i < kBlockSize; i++) { 391 random_data.push_back(dis(gen)); 392 } 393 394 for (int i = 0; i < 2; i++) { 395 chromeos::Blob data_to_test = i == 0 ? random_data : ones; 396 // The old_extents will be initialized with 0. 397 EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, 398 data_to_test)); 399 400 chromeos::Blob data; 401 DeltaArchiveManifest_InstallOperation op; 402 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 403 old_part_.path, 404 new_part_.path, 405 old_extents, 406 new_extents, 407 true, // bsdiff_allowed 408 &data, 409 &op, 410 false)); // src_ops_allowed 411 EXPECT_FALSE(data.empty()); 412 413 EXPECT_TRUE(op.has_type()); 414 const DeltaArchiveManifest_InstallOperation_Type expected_type = 415 (i == 0 ? DeltaArchiveManifest_InstallOperation_Type_REPLACE : 416 DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ); 417 EXPECT_EQ(expected_type, op.type()); 418 EXPECT_FALSE(op.has_data_offset()); 419 EXPECT_FALSE(op.has_data_length()); 420 EXPECT_EQ(0, op.src_extents_size()); 421 EXPECT_FALSE(op.has_src_length()); 422 EXPECT_EQ(1, op.dst_extents_size()); 423 EXPECT_EQ(data_to_test.size(), op.dst_length()); 424 EXPECT_EQ(1, BlocksInExtents(op.dst_extents())); 425 } 426} 427 428TEST_F(DeltaDiffUtilsTest, SourceCopyTest) { 429 // Makes sure SOURCE_COPY operations are emitted whenever src_ops_allowed 430 // is true. It is the same setup as MoveSmallTest, which checks that 431 // the operation is well-formed. 432 chromeos::Blob data_blob(kBlockSize); 433 test_utils::FillWithData(&data_blob); 434 435 // The old file is on a different block than the new one. 436 vector<Extent> old_extents = { ExtentForRange(11, 1) }; 437 vector<Extent> new_extents = { ExtentForRange(1, 1) }; 438 439 EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob)); 440 EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob)); 441 442 chromeos::Blob data; 443 DeltaArchiveManifest_InstallOperation op; 444 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 445 old_part_.path, 446 new_part_.path, 447 old_extents, 448 new_extents, 449 true, // bsdiff_allowed 450 &data, 451 &op, 452 true)); // src_ops_allowed 453 EXPECT_TRUE(data.empty()); 454 455 EXPECT_TRUE(op.has_type()); 456 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY, op.type()); 457} 458 459TEST_F(DeltaDiffUtilsTest, SourceBsdiffTest) { 460 // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed 461 // is true. It is the same setup as BsdiffSmallTest, which checks 462 // that the operation is well-formed. 463 chromeos::Blob data_blob(kBlockSize); 464 test_utils::FillWithData(&data_blob); 465 466 // The old file is on a different block than the new one. 467 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 468 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 469 470 EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob)); 471 // Modify one byte in the new file. 472 data_blob[0]++; 473 EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob)); 474 475 chromeos::Blob data; 476 DeltaArchiveManifest_InstallOperation op; 477 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 478 old_part_.path, 479 new_part_.path, 480 old_extents, 481 new_extents, 482 true, // bsdiff_allowed 483 &data, 484 &op, 485 true)); // src_ops_allowed 486 487 EXPECT_FALSE(data.empty()); 488 EXPECT_TRUE(op.has_type()); 489 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF, 490 op.type()); 491} 492 493TEST_F(DeltaDiffUtilsTest, IsNoopOperationTest) { 494 DeltaArchiveManifest_InstallOperation op; 495 op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ); 496 EXPECT_FALSE(diff_utils::IsNoopOperation(op)); 497 op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE); 498 EXPECT_TRUE(diff_utils::IsNoopOperation(op)); 499 *(op.add_src_extents()) = ExtentForRange(3, 2); 500 *(op.add_dst_extents()) = ExtentForRange(3, 2); 501 EXPECT_TRUE(diff_utils::IsNoopOperation(op)); 502 *(op.add_src_extents()) = ExtentForRange(7, 5); 503 *(op.add_dst_extents()) = ExtentForRange(7, 5); 504 EXPECT_TRUE(diff_utils::IsNoopOperation(op)); 505 *(op.add_src_extents()) = ExtentForRange(20, 2); 506 *(op.add_dst_extents()) = ExtentForRange(20, 1); 507 *(op.add_dst_extents()) = ExtentForRange(21, 1); 508 EXPECT_TRUE(diff_utils::IsNoopOperation(op)); 509 *(op.add_src_extents()) = ExtentForRange(24, 1); 510 *(op.add_dst_extents()) = ExtentForRange(25, 1); 511 EXPECT_FALSE(diff_utils::IsNoopOperation(op)); 512} 513 514TEST_F(DeltaDiffUtilsTest, FilterNoopOperations) { 515 AnnotatedOperation aop1; 516 aop1.op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ); 517 *(aop1.op.add_dst_extents()) = ExtentForRange(3, 2); 518 aop1.name = "aop1"; 519 520 AnnotatedOperation aop2 = aop1; 521 aop2.name = "aop2"; 522 523 AnnotatedOperation noop; 524 noop.op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE); 525 *(noop.op.add_src_extents()) = ExtentForRange(3, 2); 526 *(noop.op.add_dst_extents()) = ExtentForRange(3, 2); 527 noop.name = "noop"; 528 529 vector<AnnotatedOperation> ops = {noop, aop1, noop, noop, aop2, noop}; 530 diff_utils::FilterNoopOperations(&ops); 531 EXPECT_EQ(2u, ops.size()); 532 EXPECT_EQ("aop1", ops[0].name); 533 EXPECT_EQ("aop2", ops[1].name); 534} 535 536// Test the simple case where all the blocks are different and no new blocks are 537// zeroed. 538TEST_F(DeltaDiffUtilsTest, NoZeroedOrUniqueBlocksDetected) { 539 InitializePartitionWithUniqueBlocks(old_part_, block_size_, 5); 540 InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42); 541 542 EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1, // chunk_blocks 543 false)); // src_ops_allowed 544 545 EXPECT_EQ(0, old_visited_blocks_.blocks()); 546 EXPECT_EQ(0, new_visited_blocks_.blocks()); 547 EXPECT_EQ(0, blob_size_); 548 EXPECT_TRUE(aops_.empty()); 549} 550 551// Test that when the partitions have identical blocks in the same positions no 552// MOVE operation is performed and all the blocks are handled. 553TEST_F(DeltaDiffUtilsTest, IdenticalPartitionsDontMove) { 554 InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42); 555 InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42); 556 557 // Mark some of the blocks as already visited. 558 vector<Extent> already_visited = {ExtentForRange(5, 10), 559 ExtentForRange(25, 10)}; 560 old_visited_blocks_.AddExtents(already_visited); 561 new_visited_blocks_.AddExtents(already_visited); 562 563 // Most of the blocks rest in the same place, but there's no need for MOVE 564 // operations on those blocks. 565 EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1, // chunk_blocks 566 false)); // src_ops_allowed 567 568 EXPECT_EQ(kDefaultBlockCount, old_visited_blocks_.blocks()); 569 EXPECT_EQ(kDefaultBlockCount, new_visited_blocks_.blocks()); 570 EXPECT_EQ(0, blob_size_); 571 EXPECT_TRUE(aops_.empty()); 572} 573 574// Test that when the partitions have identical blocks in the same positions 575// MOVE operation is performed and all the blocks are handled. 576TEST_F(DeltaDiffUtilsTest, IdenticalBlocksAreCopiedFromSource) { 577 // We use a smaller partition for this test. 578 old_part_.size = kBlockSize * 50; 579 new_part_.size = kBlockSize * 50; 580 581 InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42); 582 InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42); 583 584 // Mark some of the blocks as already visited. 585 vector<Extent> already_visited = {ExtentForRange(5, 5), 586 ExtentForRange(25, 7)}; 587 old_visited_blocks_.AddExtents(already_visited); 588 new_visited_blocks_.AddExtents(already_visited); 589 590 // Override some of the old blocks with different data. 591 vector<Extent> different_blocks = {ExtentForRange(40, 5)}; 592 EXPECT_TRUE(WriteExtents(old_part_.path, different_blocks, kBlockSize, 593 chromeos::Blob(5 * kBlockSize, 'a'))); 594 595 EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(10, // chunk_blocks 596 true)); // src_ops_allowed 597 598 ExtentRanges expected_ranges; 599 expected_ranges.AddExtent(ExtentForRange(0, 50)); 600 expected_ranges.SubtractExtents(different_blocks); 601 602 EXPECT_EQ(expected_ranges.extent_set(), old_visited_blocks_.extent_set()); 603 EXPECT_EQ(expected_ranges.extent_set(), new_visited_blocks_.extent_set()); 604 EXPECT_EQ(0, blob_size_); 605 606 // We expect all the blocks that we didn't override with |different_blocks| 607 // and that we didn't mark as visited in |already_visited| to match and have a 608 // SOURCE_COPY operation chunked at 10 blocks. 609 vector<Extent> expected_op_extents = { 610 ExtentForRange(0, 5), 611 ExtentForRange(10, 10), 612 ExtentForRange(20, 5), 613 ExtentForRange(32, 8), 614 ExtentForRange(45, 5), 615 }; 616 617 EXPECT_EQ(expected_op_extents.size(), aops_.size()); 618 for (size_t i = 0; i < aops_.size() && i < expected_op_extents.size(); ++i) { 619 SCOPED_TRACE(base::StringPrintf("Failed on operation number %" PRIuS, i)); 620 const AnnotatedOperation& aop = aops_[i]; 621 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY, 622 aop.op.type()); 623 EXPECT_EQ(1, aop.op.src_extents_size()); 624 EXPECT_EQ(expected_op_extents[i], aop.op.src_extents(0)); 625 EXPECT_EQ(1, aop.op.dst_extents_size()); 626 EXPECT_EQ(expected_op_extents[i], aop.op.dst_extents(0)); 627 } 628} 629 630TEST_F(DeltaDiffUtilsTest, IdenticalBlocksAreCopiedInOder) { 631 // We use a smaller partition for this test. 632 old_part_.size = block_size_ * 50; 633 new_part_.size = block_size_ * 50; 634 635 // Create two identical partitions with 5 copies of the same unique "file". 636 chromeos::Blob file_data(block_size_ * 10, 'a'); 637 for (size_t offset = 0; offset < file_data.size(); offset += block_size_) 638 file_data[offset] = 'a' + offset / block_size_; 639 640 chromeos::Blob partition_data(old_part_.size); 641 for (size_t offset = 0; offset < partition_data.size(); 642 offset += file_data.size()) { 643 std::copy(file_data.begin(), file_data.end(), 644 partition_data.begin() + offset); 645 } 646 EXPECT_TRUE(test_utils::WriteFileVector(old_part_.path, partition_data)); 647 EXPECT_TRUE(test_utils::WriteFileVector(new_part_.path, partition_data)); 648 649 EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1, // chunk_blocks 650 true)); // src_ops_allowed 651 652 // There should be only one SOURCE_COPY, for the whole partition and the 653 // source extents should cover only the first copy of the source file since 654 // we prefer to re-read files (maybe cached) instead of continue reading the 655 // rest of the partition. 656 EXPECT_EQ(1, aops_.size()); 657 const AnnotatedOperation& aop = aops_[0]; 658 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY, 659 aop.op.type()); 660 EXPECT_EQ(5, aop.op.src_extents_size()); 661 for (int i = 0; i < aop.op.src_extents_size(); ++i) { 662 EXPECT_EQ(ExtentForRange(0, 10), aop.op.src_extents(i)); 663 } 664 665 EXPECT_EQ(1, aop.op.dst_extents_size()); 666 EXPECT_EQ(ExtentForRange(0, 50), aop.op.dst_extents(0)); 667 668 EXPECT_EQ(0, blob_size_); 669} 670 671// Test that all blocks with zeros are handled separately using REPLACE_BZ 672// operations unless they are not moved. 673TEST_F(DeltaDiffUtilsTest, ZeroBlocksUseReplaceBz) { 674 InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42); 675 InitializePartitionWithUniqueBlocks(new_part_, block_size_, 5); 676 677 // We set four ranges of blocks with zeros: a single block, a range that fits 678 // in the chunk size, a range that doesn't and finally a range of zeros that 679 // was also zeros in the old image. 680 vector<Extent> new_zeros = { 681 ExtentForRange(10, 1), 682 ExtentForRange(20, 4), 683 // The last range is split since the old image has zeros in part of it. 684 ExtentForRange(30, 20), 685 }; 686 chromeos::Blob zeros_data(BlocksInExtents(new_zeros) * block_size_, '\0'); 687 EXPECT_TRUE(WriteExtents(new_part_.path, new_zeros, block_size_, zeros_data)); 688 689 vector<Extent> old_zeros = vector<Extent>{ExtentForRange(43, 7)}; 690 EXPECT_TRUE(WriteExtents(old_part_.path, old_zeros, block_size_, zeros_data)); 691 692 EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(5, // chunk_blocks 693 false)); // src_ops_allowed 694 695 // Zeroed blocks from old_visited_blocks_ were copied over, so me actually 696 // use them regardless of the trivial MOVE operation not being emitted. 697 EXPECT_EQ(old_zeros, 698 old_visited_blocks_.GetExtentsForBlockCount( 699 old_visited_blocks_.blocks())); 700 701 // All the new zeroed blocks should be used, part with REPLACE_BZ and part 702 // trivial MOVE operations (not included). 703 EXPECT_EQ(new_zeros, 704 new_visited_blocks_.GetExtentsForBlockCount( 705 new_visited_blocks_.blocks())); 706 707 vector<Extent> expected_op_extents = { 708 ExtentForRange(10, 1), 709 ExtentForRange(20, 4), 710 // This range should be split. 711 ExtentForRange(30, 5), 712 ExtentForRange(35, 5), 713 ExtentForRange(40, 3), 714 }; 715 716 EXPECT_EQ(expected_op_extents.size(), aops_.size()); 717 for (size_t i = 0; i < aops_.size() && i < expected_op_extents.size(); ++i) { 718 SCOPED_TRACE(base::StringPrintf("Failed on operation number %" PRIuS, i)); 719 const AnnotatedOperation& aop = aops_[i]; 720 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ, 721 aop.op.type()); 722 EXPECT_EQ(0, aop.op.src_extents_size()); 723 EXPECT_EQ(1, aop.op.dst_extents_size()); 724 EXPECT_EQ(expected_op_extents[i], aop.op.dst_extents(0)); 725 } 726 EXPECT_NE(0, blob_size_); 727} 728 729TEST_F(DeltaDiffUtilsTest, ShuffledBlocksAreTracked) { 730 vector<uint64_t> permutation = {0, 1, 5, 6, 7, 2, 3, 4, 9, 10, 11, 12, 8}; 731 vector<Extent> perm_extents; 732 for (uint64_t x : permutation) 733 AppendBlockToExtents(&perm_extents, x); 734 735 // We use a smaller partition for this test. 736 old_part_.size = block_size_ * permutation.size(); 737 new_part_.size = block_size_ * permutation.size(); 738 InitializePartitionWithUniqueBlocks(new_part_, block_size_, 123); 739 740 // We initialize the old_part_ with the blocks from new_part but in the 741 // |permutation| order. Block i in the old_part_ will contain the same data 742 // as block permutation[i] in the new_part_. 743 chromeos::Blob new_contents; 744 EXPECT_TRUE(utils::ReadFile(new_part_.path, &new_contents)); 745 EXPECT_TRUE(WriteExtents(old_part_.path, perm_extents, block_size_, 746 new_contents)); 747 748 EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1, // chunk_blocks 749 true)); // src_ops_allowed 750 751 EXPECT_EQ(permutation.size(), old_visited_blocks_.blocks()); 752 EXPECT_EQ(permutation.size(), new_visited_blocks_.blocks()); 753 754 // There should be only one SOURCE_COPY, with a complicate list of extents. 755 EXPECT_EQ(1, aops_.size()); 756 const AnnotatedOperation& aop = aops_[0]; 757 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY, 758 aop.op.type()); 759 vector<Extent> aop_src_extents; 760 ExtentsToVector(aop.op.src_extents(), &aop_src_extents); 761 EXPECT_EQ(perm_extents, aop_src_extents); 762 763 EXPECT_EQ(1, aop.op.dst_extents_size()); 764 EXPECT_EQ(ExtentForRange(0, permutation.size()), aop.op.dst_extents(0)); 765 766 EXPECT_EQ(0, blob_size_); 767} 768 769} // namespace chromeos_update_engine 770