delta_diff_utils_unittest.cc revision a12ee11c78ac6d7c2605921a4006b6a7416e0c35
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    BlobFileWriter blob_file(blob_fd_, &blob_size_);
119    return diff_utils::DeltaMovedAndZeroBlocks(
120        &aops_,
121        old_part_.path,
122        new_part_.path,
123        old_part_.size / block_size_,
124        new_part_.size / block_size_,
125        chunk_blocks,
126        src_ops_allowed,
127        &blob_file,
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  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(InstallOperation::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  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(InstallOperation::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  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(InstallOperation::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  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(InstallOperation::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  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(InstallOperation::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    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 InstallOperation_Type expected_type =
415        (i == 0 ? InstallOperation::REPLACE : InstallOperation::REPLACE_BZ);
416    EXPECT_EQ(expected_type, op.type());
417    EXPECT_FALSE(op.has_data_offset());
418    EXPECT_FALSE(op.has_data_length());
419    EXPECT_EQ(0, op.src_extents_size());
420    EXPECT_FALSE(op.has_src_length());
421    EXPECT_EQ(1, op.dst_extents_size());
422    EXPECT_EQ(data_to_test.size(), op.dst_length());
423    EXPECT_EQ(1, BlocksInExtents(op.dst_extents()));
424  }
425}
426
427TEST_F(DeltaDiffUtilsTest, SourceCopyTest) {
428  // Makes sure SOURCE_COPY operations are emitted whenever src_ops_allowed
429  // is true. It is the same setup as MoveSmallTest, which checks that
430  // the operation is well-formed.
431  chromeos::Blob data_blob(kBlockSize);
432  test_utils::FillWithData(&data_blob);
433
434  // The old file is on a different block than the new one.
435  vector<Extent> old_extents = { ExtentForRange(11, 1) };
436  vector<Extent> new_extents = { ExtentForRange(1, 1) };
437
438  EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob));
439  EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob));
440
441  chromeos::Blob data;
442  InstallOperation op;
443  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
444      old_part_.path,
445      new_part_.path,
446      old_extents,
447      new_extents,
448      true,  // bsdiff_allowed
449      &data,
450      &op,
451      true));  // src_ops_allowed
452  EXPECT_TRUE(data.empty());
453
454  EXPECT_TRUE(op.has_type());
455  EXPECT_EQ(InstallOperation::SOURCE_COPY, op.type());
456}
457
458TEST_F(DeltaDiffUtilsTest, SourceBsdiffTest) {
459  // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed
460  // is true. It is the same setup as BsdiffSmallTest, which checks
461  // that the operation is well-formed.
462  chromeos::Blob data_blob(kBlockSize);
463  test_utils::FillWithData(&data_blob);
464
465  // The old file is on a different block than the new one.
466  vector<Extent> old_extents = { ExtentForRange(1, 1) };
467  vector<Extent> new_extents = { ExtentForRange(2, 1) };
468
469  EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob));
470  // Modify one byte in the new file.
471  data_blob[0]++;
472  EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob));
473
474  chromeos::Blob data;
475  InstallOperation op;
476  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
477      old_part_.path,
478      new_part_.path,
479      old_extents,
480      new_extents,
481      true,  // bsdiff_allowed
482      &data,
483      &op,
484      true));  // src_ops_allowed
485
486  EXPECT_FALSE(data.empty());
487  EXPECT_TRUE(op.has_type());
488  EXPECT_EQ(InstallOperation::SOURCE_BSDIFF, op.type());
489}
490
491TEST_F(DeltaDiffUtilsTest, IsNoopOperationTest) {
492  InstallOperation op;
493  op.set_type(InstallOperation::REPLACE_BZ);
494  EXPECT_FALSE(diff_utils::IsNoopOperation(op));
495  op.set_type(InstallOperation::MOVE);
496  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
497  *(op.add_src_extents()) = ExtentForRange(3, 2);
498  *(op.add_dst_extents()) = ExtentForRange(3, 2);
499  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
500  *(op.add_src_extents()) = ExtentForRange(7, 5);
501  *(op.add_dst_extents()) = ExtentForRange(7, 5);
502  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
503  *(op.add_src_extents()) = ExtentForRange(20, 2);
504  *(op.add_dst_extents()) = ExtentForRange(20, 1);
505  *(op.add_dst_extents()) = ExtentForRange(21, 1);
506  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
507  *(op.add_src_extents()) = ExtentForRange(24, 1);
508  *(op.add_dst_extents()) = ExtentForRange(25, 1);
509  EXPECT_FALSE(diff_utils::IsNoopOperation(op));
510}
511
512TEST_F(DeltaDiffUtilsTest, FilterNoopOperations) {
513  AnnotatedOperation aop1;
514  aop1.op.set_type(InstallOperation::REPLACE_BZ);
515  *(aop1.op.add_dst_extents()) = ExtentForRange(3, 2);
516  aop1.name = "aop1";
517
518  AnnotatedOperation aop2 = aop1;
519  aop2.name = "aop2";
520
521  AnnotatedOperation noop;
522  noop.op.set_type(InstallOperation::MOVE);
523  *(noop.op.add_src_extents()) = ExtentForRange(3, 2);
524  *(noop.op.add_dst_extents()) = ExtentForRange(3, 2);
525  noop.name = "noop";
526
527  vector<AnnotatedOperation> ops = {noop, aop1, noop, noop, aop2, noop};
528  diff_utils::FilterNoopOperations(&ops);
529  EXPECT_EQ(2u, ops.size());
530  EXPECT_EQ("aop1", ops[0].name);
531  EXPECT_EQ("aop2", ops[1].name);
532}
533
534// Test the simple case where all the blocks are different and no new blocks are
535// zeroed.
536TEST_F(DeltaDiffUtilsTest, NoZeroedOrUniqueBlocksDetected) {
537  InitializePartitionWithUniqueBlocks(old_part_, block_size_, 5);
538  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42);
539
540  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
541                                         false));  // src_ops_allowed
542
543  EXPECT_EQ(0, old_visited_blocks_.blocks());
544  EXPECT_EQ(0, new_visited_blocks_.blocks());
545  EXPECT_EQ(0, blob_size_);
546  EXPECT_TRUE(aops_.empty());
547}
548
549// Test that when the partitions have identical blocks in the same positions no
550// MOVE operation is performed and all the blocks are handled.
551TEST_F(DeltaDiffUtilsTest, IdenticalPartitionsDontMove) {
552  InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42);
553  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42);
554
555  // Mark some of the blocks as already visited.
556  vector<Extent> already_visited = {ExtentForRange(5, 10),
557                                    ExtentForRange(25, 10)};
558  old_visited_blocks_.AddExtents(already_visited);
559  new_visited_blocks_.AddExtents(already_visited);
560
561  // Most of the blocks rest in the same place, but there's no need for MOVE
562  // operations on those blocks.
563  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
564                                         false));  // src_ops_allowed
565
566  EXPECT_EQ(kDefaultBlockCount, old_visited_blocks_.blocks());
567  EXPECT_EQ(kDefaultBlockCount, new_visited_blocks_.blocks());
568  EXPECT_EQ(0, blob_size_);
569  EXPECT_TRUE(aops_.empty());
570}
571
572// Test that when the partitions have identical blocks in the same positions
573// MOVE operation is performed and all the blocks are handled.
574TEST_F(DeltaDiffUtilsTest, IdenticalBlocksAreCopiedFromSource) {
575  // We use a smaller partition for this test.
576  old_part_.size = kBlockSize * 50;
577  new_part_.size = kBlockSize * 50;
578
579  InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42);
580  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42);
581
582  // Mark some of the blocks as already visited.
583  vector<Extent> already_visited = {ExtentForRange(5, 5),
584                                    ExtentForRange(25, 7)};
585  old_visited_blocks_.AddExtents(already_visited);
586  new_visited_blocks_.AddExtents(already_visited);
587
588  // Override some of the old blocks with different data.
589  vector<Extent> different_blocks = {ExtentForRange(40, 5)};
590  EXPECT_TRUE(WriteExtents(old_part_.path, different_blocks, kBlockSize,
591                           chromeos::Blob(5 * kBlockSize, 'a')));
592
593  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(10,  // chunk_blocks
594                                         true));  // src_ops_allowed
595
596  ExtentRanges expected_ranges;
597  expected_ranges.AddExtent(ExtentForRange(0, 50));
598  expected_ranges.SubtractExtents(different_blocks);
599
600  EXPECT_EQ(expected_ranges.extent_set(), old_visited_blocks_.extent_set());
601  EXPECT_EQ(expected_ranges.extent_set(), new_visited_blocks_.extent_set());
602  EXPECT_EQ(0, blob_size_);
603
604  // We expect all the blocks that we didn't override with |different_blocks|
605  // and that we didn't mark as visited in |already_visited| to match and have a
606  // SOURCE_COPY operation chunked at 10 blocks.
607  vector<Extent> expected_op_extents = {
608      ExtentForRange(0, 5),
609      ExtentForRange(10, 10),
610      ExtentForRange(20, 5),
611      ExtentForRange(32, 8),
612      ExtentForRange(45, 5),
613  };
614
615  EXPECT_EQ(expected_op_extents.size(), aops_.size());
616  for (size_t i = 0; i < aops_.size() && i < expected_op_extents.size(); ++i) {
617    SCOPED_TRACE(base::StringPrintf("Failed on operation number %" PRIuS, i));
618    const AnnotatedOperation& aop = aops_[i];
619    EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
620    EXPECT_EQ(1, aop.op.src_extents_size());
621    EXPECT_EQ(expected_op_extents[i], aop.op.src_extents(0));
622    EXPECT_EQ(1, aop.op.dst_extents_size());
623    EXPECT_EQ(expected_op_extents[i], aop.op.dst_extents(0));
624  }
625}
626
627TEST_F(DeltaDiffUtilsTest, IdenticalBlocksAreCopiedInOder) {
628  // We use a smaller partition for this test.
629  old_part_.size = block_size_ * 50;
630  new_part_.size = block_size_ * 50;
631
632  // Create two identical partitions with 5 copies of the same unique "file".
633  chromeos::Blob file_data(block_size_ * 10, 'a');
634  for (size_t offset = 0; offset < file_data.size(); offset += block_size_)
635    file_data[offset] = 'a' + offset / block_size_;
636
637  chromeos::Blob partition_data(old_part_.size);
638  for (size_t offset = 0; offset < partition_data.size();
639       offset += file_data.size()) {
640    std::copy(file_data.begin(), file_data.end(),
641              partition_data.begin() + offset);
642  }
643  EXPECT_TRUE(test_utils::WriteFileVector(old_part_.path, partition_data));
644  EXPECT_TRUE(test_utils::WriteFileVector(new_part_.path, partition_data));
645
646  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
647                                         true));  // src_ops_allowed
648
649  // There should be only one SOURCE_COPY, for the whole partition and the
650  // source extents should cover only the first copy of the source file since
651  // we prefer to re-read files (maybe cached) instead of continue reading the
652  // rest of the partition.
653  EXPECT_EQ(1, aops_.size());
654  const AnnotatedOperation& aop = aops_[0];
655  EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
656  EXPECT_EQ(5, aop.op.src_extents_size());
657  for (int i = 0; i < aop.op.src_extents_size(); ++i) {
658    EXPECT_EQ(ExtentForRange(0, 10), aop.op.src_extents(i));
659  }
660
661  EXPECT_EQ(1, aop.op.dst_extents_size());
662  EXPECT_EQ(ExtentForRange(0, 50), aop.op.dst_extents(0));
663
664  EXPECT_EQ(0, blob_size_);
665}
666
667// Test that all blocks with zeros are handled separately using REPLACE_BZ
668// operations unless they are not moved.
669TEST_F(DeltaDiffUtilsTest, ZeroBlocksUseReplaceBz) {
670  InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42);
671  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 5);
672
673  // We set four ranges of blocks with zeros: a single block, a range that fits
674  // in the chunk size, a range that doesn't and finally a range of zeros that
675  // was also zeros in the old image.
676  vector<Extent> new_zeros = {
677      ExtentForRange(10, 1),
678      ExtentForRange(20, 4),
679      // The last range is split since the old image has zeros in part of it.
680      ExtentForRange(30, 20),
681  };
682  chromeos::Blob zeros_data(BlocksInExtents(new_zeros) * block_size_, '\0');
683  EXPECT_TRUE(WriteExtents(new_part_.path, new_zeros, block_size_, zeros_data));
684
685  vector<Extent> old_zeros = vector<Extent>{ExtentForRange(43, 7)};
686  EXPECT_TRUE(WriteExtents(old_part_.path, old_zeros, block_size_, zeros_data));
687
688  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(5,  // chunk_blocks
689                                         false));  // src_ops_allowed
690
691  // Zeroed blocks from old_visited_blocks_ were copied over, so me actually
692  // use them regardless of the trivial MOVE operation not being emitted.
693  EXPECT_EQ(old_zeros,
694            old_visited_blocks_.GetExtentsForBlockCount(
695                old_visited_blocks_.blocks()));
696
697  // All the new zeroed blocks should be used, part with REPLACE_BZ and part
698  // trivial MOVE operations (not included).
699  EXPECT_EQ(new_zeros,
700            new_visited_blocks_.GetExtentsForBlockCount(
701                new_visited_blocks_.blocks()));
702
703  vector<Extent> expected_op_extents = {
704      ExtentForRange(10, 1),
705      ExtentForRange(20, 4),
706      // This range should be split.
707      ExtentForRange(30, 5),
708      ExtentForRange(35, 5),
709      ExtentForRange(40, 3),
710  };
711
712  EXPECT_EQ(expected_op_extents.size(), aops_.size());
713  for (size_t i = 0; i < aops_.size() && i < expected_op_extents.size(); ++i) {
714    SCOPED_TRACE(base::StringPrintf("Failed on operation number %" PRIuS, i));
715    const AnnotatedOperation& aop = aops_[i];
716    EXPECT_EQ(InstallOperation::REPLACE_BZ, aop.op.type());
717    EXPECT_EQ(0, aop.op.src_extents_size());
718    EXPECT_EQ(1, aop.op.dst_extents_size());
719    EXPECT_EQ(expected_op_extents[i], aop.op.dst_extents(0));
720  }
721  EXPECT_NE(0, blob_size_);
722}
723
724TEST_F(DeltaDiffUtilsTest, ShuffledBlocksAreTracked) {
725  vector<uint64_t> permutation = {0, 1, 5, 6, 7, 2, 3, 4, 9, 10, 11, 12, 8};
726  vector<Extent> perm_extents;
727  for (uint64_t x : permutation)
728    AppendBlockToExtents(&perm_extents, x);
729
730  // We use a smaller partition for this test.
731  old_part_.size = block_size_ * permutation.size();
732  new_part_.size = block_size_ * permutation.size();
733  InitializePartitionWithUniqueBlocks(new_part_, block_size_, 123);
734
735  // We initialize the old_part_ with the blocks from new_part but in the
736  // |permutation| order. Block i in the old_part_ will contain the same data
737  // as block permutation[i] in the new_part_.
738  chromeos::Blob new_contents;
739  EXPECT_TRUE(utils::ReadFile(new_part_.path, &new_contents));
740  EXPECT_TRUE(WriteExtents(old_part_.path, perm_extents, block_size_,
741                           new_contents));
742
743  EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1,  // chunk_blocks
744                                         true));  // src_ops_allowed
745
746  EXPECT_EQ(permutation.size(), old_visited_blocks_.blocks());
747  EXPECT_EQ(permutation.size(), new_visited_blocks_.blocks());
748
749  // There should be only one SOURCE_COPY, with a complicate list of extents.
750  EXPECT_EQ(1, aops_.size());
751  const AnnotatedOperation& aop = aops_[0];
752  EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
753  vector<Extent> aop_src_extents;
754  ExtentsToVector(aop.op.src_extents(), &aop_src_extents);
755  EXPECT_EQ(perm_extents, aop_src_extents);
756
757  EXPECT_EQ(1, aop.op.dst_extents_size());
758  EXPECT_EQ(ExtentForRange(0, permutation.size()), aop.op.dst_extents(0));
759
760  EXPECT_EQ(0, blob_size_);
761}
762
763}  // namespace chromeos_update_engine
764