// // 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 #include #include #include "update_engine/payload_consumer/payload_constants.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" namespace chromeos_update_engine { class MergeSequenceGeneratorTest : public ::testing::Test { protected: void VerifyTransfers(MergeSequenceGenerator* generator, const std::vector& expected) { ASSERT_EQ(expected, generator->operations_); } void FindDependency( std::vector transfers, std::map>* result) { std::sort(transfers.begin(), transfers.end()); MergeSequenceGenerator generator(std::move(transfers)); ASSERT_TRUE(generator.FindDependency(result)); } void GenerateSequence(std::vector transfers, const std::vector& expected) { std::sort(transfers.begin(), transfers.end()); MergeSequenceGenerator generator(std::move(transfers)); std::vector sequence; ASSERT_TRUE(generator.Generate(&sequence)); ASSERT_EQ(expected, sequence); } }; TEST_F(MergeSequenceGeneratorTest, Create) { std::vector 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 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 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 transfers = { CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)), CreateCowMergeOperation(ExtentForRange(40, 10), ExtentForRange(50, 10)), }; std::map> merge_after; FindDependency(transfers, &merge_after); ASSERT_EQ(std::set(), merge_after.at(transfers[0])); ASSERT_EQ(std::set(), 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({transfers[2]}), merge_after.at(transfers[0])); ASSERT_EQ(std::set({transfers[0], transfers[2]}), merge_after.at(transfers[1])); ASSERT_EQ(std::set({transfers[0], transfers[1]}), merge_after.at(transfers[2])); } TEST_F(MergeSequenceGeneratorTest, FindDependencyEdgeCase) { std::vector transfers = { CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)), CreateCowMergeOperation(ExtentForRange(40, 10), ExtentForRange(50, 10)), CreateCowMergeOperation(ExtentForRange(59, 10), ExtentForRange(60, 10)), }; std::map> merge_after; FindDependency(transfers, &merge_after); ASSERT_EQ(std::set(), merge_after.at(transfers[0])); ASSERT_EQ(std::set(), merge_after.at(transfers[1])); ASSERT_EQ(merge_after[transfers[2]].size(), 1U); } TEST_F(MergeSequenceGeneratorTest, FindDependency_ReusedSourceBlocks) { std::vector transfers = { CreateCowMergeOperation(ExtentForRange(5, 10), ExtentForRange(15, 10)), CreateCowMergeOperation(ExtentForRange(6, 5), ExtentForRange(30, 5)), CreateCowMergeOperation(ExtentForRange(50, 5), ExtentForRange(5, 5)), }; std::map> merge_after; FindDependency(transfers, &merge_after); ASSERT_EQ(std::set({transfers[2]}), merge_after.at(transfers[0])); ASSERT_EQ(std::set({transfers[2]}), merge_after.at(transfers[1])); } TEST_F(MergeSequenceGeneratorTest, ValidateSequence) { std::vector 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 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 expected{ transfers[0], transfers[2], transfers[1]}; GenerateSequence(transfers, expected); } TEST_F(MergeSequenceGeneratorTest, GenerateSequenceWithCycles) { std::vector 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 expected{ transfers[3], transfers[1], transfers[0]}; GenerateSequence(transfers, expected); } TEST_F(MergeSequenceGeneratorTest, GenerateSequenceMultipleCycles) { std::vector 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 expected{ transfers[1], transfers[0], transfers[4], transfers[3]}; GenerateSequence(transfers, expected); } void ValidateSplitSequence(const Extent& src_extent, const Extent& dst_extent) { std::vector 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); } } // namespace chromeos_update_engine