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