delta_diff_utils_unittest.cc revision 14158570d3995008dc93a628004118b87a6bca01
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 <gtest/gtest.h> 13 14#include "update_engine/payload_generator/delta_diff_generator.h" 15#include "update_engine/payload_generator/extent_ranges.h" 16#include "update_engine/payload_generator/extent_utils.h" 17#include "update_engine/payload_generator/fake_filesystem.h" 18#include "update_engine/test_utils.h" 19#include "update_engine/utils.h" 20 21using std::string; 22using std::unique_ptr; 23using std::vector; 24 25namespace chromeos_update_engine { 26 27namespace { 28 29// Writes the |data| in the blocks specified by |extents| on the partition 30// |part_path|. The |data| size could be smaller than the size of the blocks 31// passed. 32bool WriteExtents(const string& part_path, 33 const vector<Extent>& extents, 34 off_t block_size, 35 const chromeos::Blob& data) { 36 uint64_t offset = 0; 37 base::ScopedFILE fp(fopen(part_path.c_str(), "r+")); 38 TEST_AND_RETURN_FALSE(fp.get()); 39 40 for (const Extent& extent : extents) { 41 if (offset >= data.size()) 42 break; 43 TEST_AND_RETURN_FALSE( 44 fseek(fp.get(), extent.start_block() * block_size, SEEK_SET) == 0); 45 uint64_t to_write = std::min(extent.num_blocks() * block_size, 46 data.size() - offset); 47 TEST_AND_RETURN_FALSE( 48 fwrite(data.data() + offset, 1, to_write, fp.get()) == to_write); 49 offset += extent.num_blocks() * block_size; 50 } 51 return true; 52} 53 54} // namespace 55 56class DeltaDiffUtilsTest : public ::testing::Test { 57 protected: 58 const uint64_t kFilesystemSize = kBlockSize * 1024; 59 60 void SetUp() override { 61 old_part_path_ = "DeltaDiffUtilsTest-old_part_path-XXXXXX"; 62 CreateFilesystem(&old_fs_, &old_part_path_, kFilesystemSize); 63 64 new_part_path_ = "DeltaDiffUtilsTest-new_part_path-XXXXXX"; 65 CreateFilesystem(&old_fs_, &new_part_path_, kFilesystemSize); 66 } 67 68 void TearDown() override { 69 unlink(old_part_path_.c_str()); 70 unlink(new_part_path_.c_str()); 71 } 72 73 // Create a fake filesystem of the given size and initialize the partition 74 // holding it. 75 void CreateFilesystem(unique_ptr<FakeFilesystem>* fs, string* filename, 76 uint64_t size) { 77 string pattern = *filename; 78 ASSERT_TRUE(utils::MakeTempFile(pattern.c_str(), filename, nullptr)); 79 ASSERT_EQ(0, truncate(filename->c_str(), size)); 80 fs->reset(new FakeFilesystem(kBlockSize, size / kBlockSize)); 81 } 82 83 // Paths to old and new temporary filesystems used in the tests. 84 string old_part_path_; 85 string new_part_path_; 86 87 // FilesystemInterface fake implementations used to mock out the file/block 88 // distribution. 89 unique_ptr<FakeFilesystem> old_fs_; 90 unique_ptr<FakeFilesystem> new_fs_; 91}; 92 93TEST_F(DeltaDiffUtilsTest, MoveSmallTest) { 94 chromeos::Blob data_blob(kBlockSize); 95 test_utils::FillWithData(&data_blob); 96 97 // The old file is on a different block than the new one. 98 vector<Extent> old_extents = { ExtentForRange(11, 1) }; 99 vector<Extent> new_extents = { ExtentForRange(1, 1) }; 100 101 EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob)); 102 EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob)); 103 104 chromeos::Blob data; 105 DeltaArchiveManifest_InstallOperation op; 106 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 107 old_part_path_, 108 new_part_path_, 109 old_extents, 110 new_extents, 111 true, // bsdiff_allowed 112 &data, 113 &op, 114 false)); // src_ops_allowed 115 EXPECT_TRUE(data.empty()); 116 117 EXPECT_TRUE(op.has_type()); 118 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type()); 119 EXPECT_FALSE(op.has_data_offset()); 120 EXPECT_FALSE(op.has_data_length()); 121 EXPECT_EQ(1, op.src_extents_size()); 122 EXPECT_EQ(kBlockSize, op.src_length()); 123 EXPECT_EQ(1, op.dst_extents_size()); 124 EXPECT_EQ(kBlockSize, op.dst_length()); 125 EXPECT_EQ(BlocksInExtents(op.src_extents()), 126 BlocksInExtents(op.dst_extents())); 127 EXPECT_EQ(1, BlocksInExtents(op.dst_extents())); 128} 129 130TEST_F(DeltaDiffUtilsTest, MoveWithSameBlock) { 131 // Setup the old/new files so that it has immobile chunks; we make sure to 132 // utilize all sub-cases of such chunks: blocks 21--22 induce a split (src) 133 // and complete removal (dst), whereas blocks 24--25 induce trimming of the 134 // tail (src) and head (dst) of extents. The final block (29) is used for 135 // ensuring we properly account for the number of bytes removed in cases where 136 // the last block is partly filled. The detailed configuration: 137 // 138 // Old: [ 20 21 22 23 24 25 ] [ 28 29 ] 139 // New: [ 18 ] [ 21 22 ] [ 20 ] [ 24 25 26 ] [ 29 ] 140 // Same: ^^ ^^ ^^ ^^ ^^ 141 vector<Extent> old_extents = { 142 ExtentForRange(20, 6), 143 ExtentForRange(28, 2) }; 144 vector<Extent> new_extents = { 145 ExtentForRange(18, 1), 146 ExtentForRange(21, 2), 147 ExtentForRange(20, 1), 148 ExtentForRange(24, 3), 149 ExtentForRange(29, 1) }; 150 151 uint64_t num_blocks = BlocksInExtents(old_extents); 152 EXPECT_EQ(num_blocks, BlocksInExtents(new_extents)); 153 154 // The size of the data should match the total number of blocks. Each block 155 // has a different content. 156 chromeos::Blob file_data; 157 for (uint64_t i = 0; i < num_blocks; ++i) { 158 file_data.resize(file_data.size() + kBlockSize, 'a' + i); 159 } 160 161 EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, file_data)); 162 EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, file_data)); 163 164 chromeos::Blob data; 165 DeltaArchiveManifest_InstallOperation op; 166 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 167 old_part_path_, 168 new_part_path_, 169 old_extents, 170 new_extents, 171 true, // bsdiff_allowed 172 &data, 173 &op, 174 false)); // src_ops_allowed 175 176 EXPECT_TRUE(data.empty()); 177 178 EXPECT_TRUE(op.has_type()); 179 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type()); 180 EXPECT_FALSE(op.has_data_offset()); 181 EXPECT_FALSE(op.has_data_length()); 182 183 // The expected old and new extents that actually moved. See comment above. 184 old_extents = { 185 ExtentForRange(20, 1), 186 ExtentForRange(23, 1), 187 ExtentForRange(28, 1) }; 188 new_extents = { 189 ExtentForRange(18, 1), 190 ExtentForRange(20, 1), 191 ExtentForRange(26, 1) }; 192 num_blocks = BlocksInExtents(old_extents); 193 194 EXPECT_EQ(num_blocks * kBlockSize, op.src_length()); 195 EXPECT_EQ(num_blocks * kBlockSize, op.dst_length()); 196 197 EXPECT_EQ(old_extents.size(), op.src_extents_size()); 198 for (int i = 0; i < op.src_extents_size(); i++) { 199 EXPECT_EQ(old_extents[i].start_block(), op.src_extents(i).start_block()) 200 << "i == " << i; 201 EXPECT_EQ(old_extents[i].num_blocks(), op.src_extents(i).num_blocks()) 202 << "i == " << i; 203 } 204 205 EXPECT_EQ(new_extents.size(), op.dst_extents_size()); 206 for (int i = 0; i < op.dst_extents_size(); i++) { 207 EXPECT_EQ(new_extents[i].start_block(), op.dst_extents(i).start_block()) 208 << "i == " << i; 209 EXPECT_EQ(new_extents[i].num_blocks(), op.dst_extents(i).num_blocks()) 210 << "i == " << i; 211 } 212} 213 214TEST_F(DeltaDiffUtilsTest, BsdiffSmallTest) { 215 // Test a BSDIFF operation from block 1 to block 2. 216 chromeos::Blob data_blob(kBlockSize); 217 test_utils::FillWithData(&data_blob); 218 219 // The old file is on a different block than the new one. 220 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 221 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 222 223 EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob)); 224 // Modify one byte in the new file. 225 data_blob[0]++; 226 EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob)); 227 228 chromeos::Blob data; 229 DeltaArchiveManifest_InstallOperation op; 230 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 231 old_part_path_, 232 new_part_path_, 233 old_extents, 234 new_extents, 235 true, // bsdiff_allowed 236 &data, 237 &op, 238 false)); // src_ops_allowed 239 240 EXPECT_FALSE(data.empty()); 241 242 EXPECT_TRUE(op.has_type()); 243 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_BSDIFF, op.type()); 244 EXPECT_FALSE(op.has_data_offset()); 245 EXPECT_FALSE(op.has_data_length()); 246 EXPECT_EQ(1, op.src_extents_size()); 247 EXPECT_EQ(kBlockSize, op.src_length()); 248 EXPECT_EQ(1, op.dst_extents_size()); 249 EXPECT_EQ(kBlockSize, op.dst_length()); 250 EXPECT_EQ(BlocksInExtents(op.src_extents()), 251 BlocksInExtents(op.dst_extents())); 252 EXPECT_EQ(1, BlocksInExtents(op.dst_extents())); 253} 254 255TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedTest) { 256 // Same setup as the previous test, but this time BSDIFF operations are not 257 // allowed. 258 chromeos::Blob data_blob(kBlockSize); 259 test_utils::FillWithData(&data_blob); 260 261 // The old file is on a different block than the new one. 262 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 263 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 264 265 EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob)); 266 // Modify one byte in the new file. 267 data_blob[0]++; 268 EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob)); 269 270 chromeos::Blob data; 271 DeltaArchiveManifest_InstallOperation op; 272 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 273 old_part_path_, 274 new_part_path_, 275 old_extents, 276 new_extents, 277 false, // bsdiff_allowed 278 &data, 279 &op, 280 false)); // src_ops_allowed 281 282 EXPECT_FALSE(data.empty()); 283 284 // The point of this test is that we don't use BSDIFF the way the above 285 // did. The rest of the details are to be caught in other tests. 286 EXPECT_TRUE(op.has_type()); 287 EXPECT_NE(DeltaArchiveManifest_InstallOperation_Type_BSDIFF, op.type()); 288} 289 290TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedMoveTest) { 291 chromeos::Blob data_blob(kBlockSize); 292 test_utils::FillWithData(&data_blob); 293 294 // The old file is on a different block than the new one. 295 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 296 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 297 298 EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob)); 299 EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob)); 300 301 chromeos::Blob data; 302 DeltaArchiveManifest_InstallOperation op; 303 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 304 old_part_path_, 305 new_part_path_, 306 old_extents, 307 new_extents, 308 false, // bsdiff_allowed 309 &data, 310 &op, 311 false)); // src_ops_allowed 312 313 EXPECT_TRUE(data.empty()); 314 315 // The point of this test is that we can still use a MOVE for a file 316 // that is blacklisted. 317 EXPECT_TRUE(op.has_type()); 318 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type()); 319} 320 321TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) { 322 // The old file is on a different block than the new one. 323 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 324 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 325 326 // Make a blob that's just 1's that will compress well. 327 chromeos::Blob ones(kBlockSize, 1); 328 329 // Make a blob with random data that won't compress well. 330 chromeos::Blob random_data; 331 std::mt19937 gen(12345); 332 std::uniform_int_distribution<uint8_t> dis(0, 255); 333 for (uint32_t i = 0; i < kBlockSize; i++) { 334 random_data.push_back(dis(gen)); 335 } 336 337 for (int i = 0; i < 2; i++) { 338 chromeos::Blob data_to_test = i == 0 ? random_data : ones; 339 // The old_extents will be initialized with 0. 340 EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, 341 data_to_test)); 342 343 chromeos::Blob data; 344 DeltaArchiveManifest_InstallOperation op; 345 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 346 old_part_path_, 347 new_part_path_, 348 old_extents, 349 new_extents, 350 true, // bsdiff_allowed 351 &data, 352 &op, 353 false)); // src_ops_allowed 354 EXPECT_FALSE(data.empty()); 355 356 EXPECT_TRUE(op.has_type()); 357 const DeltaArchiveManifest_InstallOperation_Type expected_type = 358 (i == 0 ? DeltaArchiveManifest_InstallOperation_Type_REPLACE : 359 DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ); 360 EXPECT_EQ(expected_type, op.type()); 361 EXPECT_FALSE(op.has_data_offset()); 362 EXPECT_FALSE(op.has_data_length()); 363 EXPECT_EQ(0, op.src_extents_size()); 364 EXPECT_FALSE(op.has_src_length()); 365 EXPECT_EQ(1, op.dst_extents_size()); 366 EXPECT_EQ(data_to_test.size(), op.dst_length()); 367 EXPECT_EQ(1, BlocksInExtents(op.dst_extents())); 368 } 369} 370 371TEST_F(DeltaDiffUtilsTest, SourceCopyTest) { 372 // Makes sure SOURCE_COPY operations are emitted whenever src_ops_allowed 373 // is true. It is the same setup as MoveSmallTest, which checks that 374 // the operation is well-formed. 375 chromeos::Blob data_blob(kBlockSize); 376 test_utils::FillWithData(&data_blob); 377 378 // The old file is on a different block than the new one. 379 vector<Extent> old_extents = { ExtentForRange(11, 1) }; 380 vector<Extent> new_extents = { ExtentForRange(1, 1) }; 381 382 EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob)); 383 EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob)); 384 385 chromeos::Blob data; 386 DeltaArchiveManifest_InstallOperation op; 387 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 388 old_part_path_, 389 new_part_path_, 390 old_extents, 391 new_extents, 392 true, // bsdiff_allowed 393 &data, 394 &op, 395 true)); // src_ops_allowed 396 EXPECT_TRUE(data.empty()); 397 398 EXPECT_TRUE(op.has_type()); 399 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY, op.type()); 400} 401 402TEST_F(DeltaDiffUtilsTest, SourceBsdiffTest) { 403 // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed 404 // is true. It is the same setup as BsdiffSmallTest, which checks 405 // that the operation is well-formed. 406 chromeos::Blob data_blob(kBlockSize); 407 test_utils::FillWithData(&data_blob); 408 409 // The old file is on a different block than the new one. 410 vector<Extent> old_extents = { ExtentForRange(1, 1) }; 411 vector<Extent> new_extents = { ExtentForRange(2, 1) }; 412 413 EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob)); 414 // Modify one byte in the new file. 415 data_blob[0]++; 416 EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob)); 417 418 chromeos::Blob data; 419 DeltaArchiveManifest_InstallOperation op; 420 EXPECT_TRUE(diff_utils::ReadExtentsToDiff( 421 old_part_path_, 422 new_part_path_, 423 old_extents, 424 new_extents, 425 true, // bsdiff_allowed 426 &data, 427 &op, 428 true)); // src_ops_allowed 429 430 EXPECT_FALSE(data.empty()); 431 EXPECT_TRUE(op.has_type()); 432 EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF, 433 op.type()); 434} 435 436TEST_F(DeltaDiffUtilsTest, IsNoopOperationTest) { 437 DeltaArchiveManifest_InstallOperation op; 438 op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ); 439 EXPECT_FALSE(diff_utils::IsNoopOperation(op)); 440 op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE); 441 EXPECT_TRUE(diff_utils::IsNoopOperation(op)); 442 *(op.add_src_extents()) = ExtentForRange(3, 2); 443 *(op.add_dst_extents()) = ExtentForRange(3, 2); 444 EXPECT_TRUE(diff_utils::IsNoopOperation(op)); 445 *(op.add_src_extents()) = ExtentForRange(7, 5); 446 *(op.add_dst_extents()) = ExtentForRange(7, 5); 447 EXPECT_TRUE(diff_utils::IsNoopOperation(op)); 448 *(op.add_src_extents()) = ExtentForRange(20, 2); 449 *(op.add_dst_extents()) = ExtentForRange(20, 1); 450 *(op.add_dst_extents()) = ExtentForRange(21, 1); 451 EXPECT_TRUE(diff_utils::IsNoopOperation(op)); 452 *(op.add_src_extents()) = ExtentForRange(24, 1); 453 *(op.add_dst_extents()) = ExtentForRange(25, 1); 454 EXPECT_FALSE(diff_utils::IsNoopOperation(op)); 455} 456 457TEST_F(DeltaDiffUtilsTest, FilterNoopOperations) { 458 AnnotatedOperation aop1; 459 aop1.op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ); 460 *(aop1.op.add_dst_extents()) = ExtentForRange(3, 2); 461 aop1.name = "aop1"; 462 463 AnnotatedOperation aop2 = aop1; 464 aop2.name = "aop2"; 465 466 AnnotatedOperation noop; 467 noop.op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE); 468 *(noop.op.add_src_extents()) = ExtentForRange(3, 2); 469 *(noop.op.add_dst_extents()) = ExtentForRange(3, 2); 470 noop.name = "noop"; 471 472 vector<AnnotatedOperation> ops = {noop, aop1, noop, noop, aop2, noop}; 473 diff_utils::FilterNoopOperations(&ops); 474 EXPECT_EQ(2u, ops.size()); 475 EXPECT_EQ("aop1", ops[0].name); 476 EXPECT_EQ("aop2", ops[1].name); 477} 478 479} // namespace chromeos_update_engine 480