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