blob: 86d4fdd128227cf79422a1c1f5d6e6fe9ec970fd [file] [log] [blame]
//
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include <algorithm>
#include <vector>
#include <gtest/gtest.h>
#include "update_engine/payload_consumer/payload_constants.h"
#include "update_engine/payload_generator/delta_diff_generator.h"
#include "update_engine/payload_generator/extent_ranges.h"
#include "update_engine/payload_generator/extent_utils.h"
#include "update_engine/payload_generator/merge_sequence_generator.h"
#include "update_engine/update_metadata.pb.h"
namespace chromeos_update_engine {
CowMergeOperation CreateCowMergeOperation(const Extent& src_extent,
const Extent& dst_extent) {
return CreateCowMergeOperation(
src_extent, dst_extent, CowMergeOperation::COW_COPY);
}
class MergeSequenceGeneratorTest : public ::testing::Test {
protected:
void VerifyTransfers(MergeSequenceGenerator* generator,
const std::vector<CowMergeOperation>& expected) {
ASSERT_EQ(expected, generator->operations_);
}
void FindDependency(
std::vector<CowMergeOperation> transfers,
std::map<CowMergeOperation, std::set<CowMergeOperation>>* result) {
std::sort(transfers.begin(), transfers.end());
MergeSequenceGenerator generator(std::move(transfers));
ASSERT_TRUE(generator.FindDependency(result));
}
void GenerateSequence(std::vector<CowMergeOperation> transfers,
const std::vector<CowMergeOperation>& expected) {
std::sort(transfers.begin(), transfers.end());
MergeSequenceGenerator generator(std::move(transfers));
std::vector<CowMergeOperation> sequence;
ASSERT_TRUE(generator.Generate(&sequence));
ASSERT_EQ(expected, sequence);
}
};
TEST_F(MergeSequenceGeneratorTest, Create) {
std::vector<AnnotatedOperation> aops{{"file1", {}, {}}, {"file2", {}, {}}};
aops[0].op.set_type(InstallOperation::SOURCE_COPY);
*aops[0].op.add_src_extents() = ExtentForRange(10, 10);
*aops[0].op.add_dst_extents() = ExtentForRange(30, 10);
aops[1].op.set_type(InstallOperation::SOURCE_COPY);
*aops[1].op.add_src_extents() = ExtentForRange(20, 10);
*aops[1].op.add_dst_extents() = ExtentForRange(40, 10);
auto generator = MergeSequenceGenerator::Create(aops);
ASSERT_TRUE(generator);
std::vector<CowMergeOperation> expected = {
CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(30, 10)),
CreateCowMergeOperation(ExtentForRange(20, 10), ExtentForRange(40, 10))};
VerifyTransfers(generator.get(), expected);
*aops[1].op.add_src_extents() = ExtentForRange(30, 5);
*aops[1].op.add_dst_extents() = ExtentForRange(50, 5);
generator = MergeSequenceGenerator::Create(aops);
ASSERT_FALSE(generator);
}
TEST_F(MergeSequenceGeneratorTest, Create_SplitSource) {
InstallOperation op;
op.set_type(InstallOperation::SOURCE_COPY);
*(op.add_src_extents()) = ExtentForRange(2, 3);
*(op.add_src_extents()) = ExtentForRange(6, 1);
*(op.add_src_extents()) = ExtentForRange(8, 4);
*(op.add_dst_extents()) = ExtentForRange(10, 8);
AnnotatedOperation aop{"file1", op, {}};
auto generator = MergeSequenceGenerator::Create({aop});
ASSERT_TRUE(generator);
std::vector<CowMergeOperation> expected = {
CreateCowMergeOperation(ExtentForRange(2, 3), ExtentForRange(10, 3)),
CreateCowMergeOperation(ExtentForRange(6, 1), ExtentForRange(13, 1)),
CreateCowMergeOperation(ExtentForRange(8, 4), ExtentForRange(14, 4))};
VerifyTransfers(generator.get(), expected);
}
TEST_F(MergeSequenceGeneratorTest, FindDependency) {
std::vector<CowMergeOperation> transfers = {
CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)),
CreateCowMergeOperation(ExtentForRange(40, 10), ExtentForRange(50, 10)),
};
std::map<CowMergeOperation, std::set<CowMergeOperation>> merge_after;
FindDependency(transfers, &merge_after);
ASSERT_EQ(std::set<CowMergeOperation>(), merge_after.at(transfers[0]));
ASSERT_EQ(std::set<CowMergeOperation>(), merge_after.at(transfers[1]));
transfers = {
CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(25, 10)),
CreateCowMergeOperation(ExtentForRange(24, 5), ExtentForRange(35, 5)),
CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(15, 10)),
};
FindDependency(transfers, &merge_after);
ASSERT_EQ(std::set<CowMergeOperation>({transfers[2]}),
merge_after.at(transfers[0]));
ASSERT_EQ(std::set<CowMergeOperation>({transfers[0], transfers[2]}),
merge_after.at(transfers[1]));
ASSERT_EQ(std::set<CowMergeOperation>({transfers[0], transfers[1]}),
merge_after.at(transfers[2]));
}
TEST_F(MergeSequenceGeneratorTest, FindDependencyEdgeCase) {
std::vector<CowMergeOperation> transfers = {
CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)),
CreateCowMergeOperation(ExtentForRange(40, 10), ExtentForRange(50, 10)),
CreateCowMergeOperation(ExtentForRange(59, 10), ExtentForRange(60, 10)),
};
std::map<CowMergeOperation, std::set<CowMergeOperation>> merge_after;
FindDependency(transfers, &merge_after);
ASSERT_EQ(std::set<CowMergeOperation>(), merge_after.at(transfers[0]));
ASSERT_EQ(std::set<CowMergeOperation>(), merge_after.at(transfers[1]));
ASSERT_EQ(merge_after[transfers[2]].size(), 1U);
}
TEST_F(MergeSequenceGeneratorTest, FindDependency_ReusedSourceBlocks) {
std::vector<CowMergeOperation> transfers = {
CreateCowMergeOperation(ExtentForRange(5, 10), ExtentForRange(15, 10)),
CreateCowMergeOperation(ExtentForRange(6, 5), ExtentForRange(30, 5)),
CreateCowMergeOperation(ExtentForRange(50, 5), ExtentForRange(5, 5)),
};
std::map<CowMergeOperation, std::set<CowMergeOperation>> merge_after;
FindDependency(transfers, &merge_after);
ASSERT_EQ(std::set<CowMergeOperation>({transfers[2]}),
merge_after.at(transfers[0]));
ASSERT_EQ(std::set<CowMergeOperation>({transfers[2]}),
merge_after.at(transfers[1]));
}
TEST_F(MergeSequenceGeneratorTest, ValidateSequence) {
std::vector<CowMergeOperation> transfers = {
CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)),
CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(40, 10)),
};
// Self overlapping
ASSERT_TRUE(MergeSequenceGenerator::ValidateSequence(transfers));
transfers = {
CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(20, 10)),
CreateCowMergeOperation(ExtentForRange(15, 10), ExtentForRange(10, 10)),
};
ASSERT_FALSE(MergeSequenceGenerator::ValidateSequence(transfers));
}
TEST_F(MergeSequenceGeneratorTest, GenerateSequenceNoCycles) {
std::vector<CowMergeOperation> transfers = {
CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)),
// file3 should merge before file2
CreateCowMergeOperation(ExtentForRange(40, 5), ExtentForRange(25, 5)),
CreateCowMergeOperation(ExtentForRange(25, 10), ExtentForRange(30, 10)),
};
std::vector<CowMergeOperation> expected{
transfers[0], transfers[2], transfers[1]};
GenerateSequence(transfers, expected);
}
TEST_F(MergeSequenceGeneratorTest, GenerateSequenceWithCycles) {
std::vector<CowMergeOperation> transfers = {
CreateCowMergeOperation(ExtentForRange(25, 10), ExtentForRange(30, 10)),
CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(40, 10)),
CreateCowMergeOperation(ExtentForRange(40, 10), ExtentForRange(25, 10)),
CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)),
};
// file 1,2,3 form a cycle. And file3, whose dst ext has smallest offset,
// will be converted to raw blocks
std::vector<CowMergeOperation> expected{
transfers[3], transfers[1], transfers[0]};
GenerateSequence(transfers, expected);
}
TEST_F(MergeSequenceGeneratorTest, GenerateSequenceMultipleCycles) {
std::vector<CowMergeOperation> transfers = {
// cycle 1
CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(25, 10)),
CreateCowMergeOperation(ExtentForRange(24, 5), ExtentForRange(35, 5)),
CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(15, 10)),
// cycle 2
CreateCowMergeOperation(ExtentForRange(55, 10), ExtentForRange(60, 10)),
CreateCowMergeOperation(ExtentForRange(60, 10), ExtentForRange(70, 10)),
CreateCowMergeOperation(ExtentForRange(70, 10), ExtentForRange(55, 10)),
};
// file 3, 6 will be converted to raw.
std::vector<CowMergeOperation> expected{
transfers[1], transfers[0], transfers[4], transfers[3]};
GenerateSequence(transfers, expected);
}
void ValidateSplitSequence(const Extent& src_extent, const Extent& dst_extent) {
std::vector<CowMergeOperation> sequence;
SplitSelfOverlapping(src_extent, dst_extent, &sequence);
ExtentRanges src_extent_set;
src_extent_set.AddExtent(src_extent);
ExtentRanges dst_extent_set;
dst_extent_set.AddExtent(dst_extent);
size_t src_block_count = 0;
size_t dst_block_count = 0;
std::cout << "src_extent: " << src_extent << " dst_extent: " << dst_extent
<< '\n';
for (const auto& merge_op : sequence) {
src_extent_set.SubtractExtent(merge_op.src_extent());
dst_extent_set.SubtractExtent(merge_op.dst_extent());
src_block_count += merge_op.src_extent().num_blocks();
dst_block_count += merge_op.dst_extent().num_blocks();
std::cout << merge_op.src_extent() << " -> " << merge_op.dst_extent()
<< '\n';
ASSERT_FALSE(ExtentRanges::ExtentsOverlap(merge_op.src_extent(),
merge_op.dst_extent()));
}
std::cout << '\n';
// Check that all blocks are covered
ASSERT_EQ(src_extent_set.extent_set().size(), 0UL);
ASSERT_EQ(dst_extent_set.extent_set().size(), 0UL);
// Check that the split didn't cover extra blocks
ASSERT_EQ(src_block_count, src_extent.num_blocks());
ASSERT_EQ(dst_block_count, dst_extent.num_blocks());
}
TEST_F(MergeSequenceGeneratorTest, SplitSelfOverlappingTest) {
auto a = ExtentForRange(25, 16);
auto b = ExtentForRange(30, 16);
ValidateSplitSequence(a, b);
ValidateSplitSequence(b, a);
}
TEST_F(MergeSequenceGeneratorTest, GenerateSequenceWithXor) {
std::vector<CowMergeOperation> transfers = {
// cycle 1
CreateCowMergeOperation(ExtentForRange(10, 10),
ExtentForRange(25, 10),
CowMergeOperation::COW_XOR),
CreateCowMergeOperation(ExtentForRange(24, 5), ExtentForRange(35, 5)),
CreateCowMergeOperation(ExtentForRange(30, 10),
ExtentForRange(15, 10),
CowMergeOperation::COW_XOR),
// cycle 2
CreateCowMergeOperation(ExtentForRange(55, 10), ExtentForRange(60, 10)),
CreateCowMergeOperation(ExtentForRange(60, 10),
ExtentForRange(70, 10),
CowMergeOperation::COW_XOR),
CreateCowMergeOperation(ExtentForRange(70, 10), ExtentForRange(55, 10)),
};
// file 3, 6 will be converted to raw.
std::vector<CowMergeOperation> expected{
transfers[1], transfers[0], transfers[4], transfers[3]};
GenerateSequence(transfers, expected);
}
TEST_F(MergeSequenceGeneratorTest, CreateGeneratorWithXor) {
std::vector<AnnotatedOperation> aops;
auto& aop = aops.emplace_back();
aop.op.set_type(InstallOperation::SOURCE_BSDIFF);
*aop.op.mutable_src_extents()->Add() = ExtentForRange(10, 5);
*aop.op.mutable_dst_extents()->Add() = ExtentForRange(20, 5);
auto& xor_map = aop.xor_ops;
{
// xor_map[i] = i * kBlockSize + 123;
auto& op = xor_map.emplace_back();
*op.mutable_src_extent() = ExtentForRange(10, 5);
*op.mutable_dst_extent() = ExtentForRange(20, 5);
op.set_src_offset(123);
op.set_type(CowMergeOperation::COW_XOR);
}
auto generator = MergeSequenceGenerator::Create(aops);
ASSERT_NE(generator, nullptr);
std::vector<CowMergeOperation> sequence;
ASSERT_TRUE(generator->Generate(&sequence));
ASSERT_EQ(sequence.size(), 1UL);
ASSERT_EQ(sequence[0].src_extent().start_block(), 10UL);
ASSERT_EQ(sequence[0].dst_extent().start_block(), 20UL);
ASSERT_EQ(sequence[0].src_extent().num_blocks(), 6UL);
ASSERT_EQ(sequence[0].dst_extent().num_blocks(), 5UL);
ASSERT_EQ(sequence[0].type(), CowMergeOperation::COW_XOR);
ASSERT_EQ(sequence[0].src_offset(), 123UL);
ASSERT_TRUE(generator->ValidateSequence(sequence));
}
TEST_F(MergeSequenceGeneratorTest, CreateGeneratorWithXorMultipleExtents) {
std::vector<AnnotatedOperation> aops;
auto& aop = aops.emplace_back();
aop.op.set_type(InstallOperation::SOURCE_BSDIFF);
*aop.op.mutable_src_extents()->Add() = ExtentForRange(10, 10);
*aop.op.mutable_dst_extents()->Add() = ExtentForRange(30, 5);
*aop.op.mutable_dst_extents()->Add() = ExtentForRange(45, 5);
auto& xor_map = aop.xor_ops;
{
// xor_map[i] = i * kBlockSize + 123;
auto& op = xor_map.emplace_back();
*op.mutable_src_extent() = ExtentForRange(10, 5);
*op.mutable_dst_extent() = ExtentForRange(30, 5);
op.set_src_offset(123);
op.set_type(CowMergeOperation::COW_XOR);
}
{
// xor_map[i] = i * kBlockSize + 123;
auto& op = xor_map.emplace_back();
*op.mutable_src_extent() = ExtentForRange(15, 5);
*op.mutable_dst_extent() = ExtentForRange(45, 5);
op.set_src_offset(123);
op.set_type(CowMergeOperation::COW_XOR);
}
auto generator = MergeSequenceGenerator::Create(aops);
ASSERT_NE(generator, nullptr);
std::vector<CowMergeOperation> sequence;
ASSERT_TRUE(generator->Generate(&sequence));
ASSERT_EQ(sequence.size(), 2UL);
ASSERT_EQ(sequence[0].src_extent().start_block(), 10UL);
ASSERT_EQ(sequence[0].dst_extent().start_block(), 30UL);
ASSERT_EQ(sequence[0].src_extent().num_blocks(), 6UL);
ASSERT_EQ(sequence[0].dst_extent().num_blocks(), 5UL);
ASSERT_EQ(sequence[0].type(), CowMergeOperation::COW_XOR);
ASSERT_EQ(sequence[0].src_offset(), 123UL);
ASSERT_EQ(sequence[1].src_extent().start_block(), 15UL);
ASSERT_EQ(sequence[1].dst_extent().start_block(), 45UL);
ASSERT_EQ(sequence[1].src_extent().num_blocks(), 6UL);
ASSERT_EQ(sequence[1].dst_extent().num_blocks(), 5UL);
ASSERT_EQ(sequence[1].type(), CowMergeOperation::COW_XOR);
ASSERT_EQ(sequence[1].src_offset(), 123UL);
ASSERT_TRUE(generator->ValidateSequence(sequence));
}
TEST_F(MergeSequenceGeneratorTest, CreateGeneratorXorAppendBlock) {
std::vector<AnnotatedOperation> aops;
auto& aop = aops.emplace_back();
aop.op.set_type(InstallOperation::SOURCE_BSDIFF);
*aop.op.mutable_src_extents()->Add() = ExtentForRange(10, 10);
*aop.op.mutable_dst_extents()->Add() = ExtentForRange(20, 10);
auto& xor_map = aop.xor_ops;
{
auto& op = xor_map.emplace_back();
*op.mutable_src_extent() = ExtentForRange(10, 5);
*op.mutable_dst_extent() = ExtentForRange(20, 5);
op.set_type(CowMergeOperation::COW_XOR);
}
{
auto& op = xor_map.emplace_back();
*op.mutable_src_extent() = ExtentForRange(15, 5);
*op.mutable_dst_extent() = ExtentForRange(25, 5);
op.set_src_offset(123);
op.set_type(CowMergeOperation::COW_XOR);
}
auto generator = MergeSequenceGenerator::Create(aops);
ASSERT_NE(generator, nullptr);
std::vector<CowMergeOperation> sequence;
ASSERT_TRUE(generator->Generate(&sequence));
ASSERT_EQ(sequence.size(), 2UL);
ASSERT_EQ(sequence[0].src_extent().start_block(), 15UL);
ASSERT_EQ(sequence[0].dst_extent().start_block(), 25UL);
ASSERT_EQ(sequence[0].src_extent().num_blocks(), 6UL);
ASSERT_EQ(sequence[0].dst_extent().num_blocks(), 5UL);
ASSERT_EQ(sequence[0].type(), CowMergeOperation::COW_XOR);
ASSERT_EQ(sequence[0].src_offset(), 123UL);
ASSERT_EQ(sequence[1].src_extent().start_block(), 10UL);
ASSERT_EQ(sequence[1].dst_extent().start_block(), 20UL);
ASSERT_EQ(sequence[1].src_extent().num_blocks(), 5UL);
ASSERT_EQ(sequence[1].dst_extent().num_blocks(), 5UL);
ASSERT_EQ(sequence[1].type(), CowMergeOperation::COW_XOR);
ASSERT_TRUE(generator->ValidateSequence(sequence));
}
TEST_F(MergeSequenceGeneratorTest, CreateGeneratorXorAlreadyPlusOne) {
std::vector<AnnotatedOperation> aops;
auto& aop = aops.emplace_back();
aop.op.set_type(InstallOperation::SOURCE_BSDIFF);
*aop.op.mutable_src_extents()->Add() = ExtentForRange(10, 10);
*aop.op.mutable_dst_extents()->Add() = ExtentForRange(20, 10);
auto& xor_map = aop.xor_ops;
{
auto& op = xor_map.emplace_back();
*op.mutable_src_extent() = ExtentForRange(15, 6);
*op.mutable_dst_extent() = ExtentForRange(25, 5);
op.set_src_offset(123);
op.set_type(CowMergeOperation::COW_XOR);
}
auto generator = MergeSequenceGenerator::Create(aops);
ASSERT_NE(generator, nullptr);
std::vector<CowMergeOperation> sequence;
ASSERT_TRUE(generator->Generate(&sequence));
ASSERT_EQ(sequence.size(), 1UL);
ASSERT_EQ(sequence[0].src_extent().start_block(), 15UL);
ASSERT_EQ(sequence[0].dst_extent().start_block(), 25UL);
ASSERT_EQ(sequence[0].src_extent().num_blocks(), 6UL);
ASSERT_EQ(sequence[0].dst_extent().num_blocks(), 5UL);
ASSERT_EQ(sequence[0].type(), CowMergeOperation::COW_XOR);
ASSERT_EQ(sequence[0].src_offset(), 123UL);
ASSERT_TRUE(generator->ValidateSequence(sequence));
}
} // namespace chromeos_update_engine