| // |
| // Copyright (C) 2015 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 "update_engine/payload_generator/inplace_generator.h" |
| |
| #include <map> |
| #include <set> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/format_macros.h> |
| #include <base/logging.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <gtest/gtest.h> |
| |
| #include "update_engine/common/test_utils.h" |
| #include "update_engine/common/utils.h" |
| #include "update_engine/payload_generator/cycle_breaker.h" |
| #include "update_engine/payload_generator/delta_diff_generator.h" |
| #include "update_engine/payload_generator/delta_diff_utils.h" |
| #include "update_engine/payload_generator/extent_ranges.h" |
| #include "update_engine/payload_generator/graph_types.h" |
| #include "update_engine/payload_generator/graph_utils.h" |
| |
| using std::map; |
| using std::set; |
| using std::string; |
| using std::stringstream; |
| using std::vector; |
| |
| namespace chromeos_update_engine { |
| |
| using Block = InplaceGenerator::Block; |
| |
| namespace { |
| |
| void GenVertex(Vertex* out, |
| const vector<Extent>& src_extents, |
| const vector<Extent>& dst_extents, |
| const string& path, |
| InstallOperation_Type type) { |
| out->aop.op.set_type(type); |
| out->aop.name = path; |
| StoreExtents(src_extents, out->aop.op.mutable_src_extents()); |
| StoreExtents(dst_extents, out->aop.op.mutable_dst_extents()); |
| } |
| |
| vector<Extent> VectOfExt(uint64_t start_block, uint64_t num_blocks) { |
| return vector<Extent>(1, ExtentForRange(start_block, num_blocks)); |
| } |
| |
| EdgeProperties EdgeWithReadDep(const vector<Extent>& extents) { |
| EdgeProperties ret; |
| ret.extents = extents; |
| return ret; |
| } |
| |
| EdgeProperties EdgeWithWriteDep(const vector<Extent>& extents) { |
| EdgeProperties ret; |
| ret.write_extents = extents; |
| return ret; |
| } |
| |
| template<typename T> |
| void DumpVect(const vector<T>& vect) { |
| stringstream ss(stringstream::out); |
| for (typename vector<T>::const_iterator it = vect.begin(), e = vect.end(); |
| it != e; ++it) { |
| ss << *it << ", "; |
| } |
| LOG(INFO) << "{" << ss.str() << "}"; |
| } |
| |
| void AppendExtent(vector<Extent>* vect, uint64_t start, uint64_t length) { |
| vect->resize(vect->size() + 1); |
| vect->back().set_start_block(start); |
| vect->back().set_num_blocks(length); |
| } |
| |
| void OpAppendExtent(InstallOperation* op, uint64_t start, uint64_t length) { |
| Extent* extent = op->add_src_extents(); |
| extent->set_start_block(start); |
| extent->set_num_blocks(length); |
| } |
| |
| } // namespace |
| |
| class InplaceGeneratorTest : public ::testing::Test { |
| protected: |
| // Initialize |blob_path_|, |blob_file_size_| and |blob_file_fd_| variables |
| // with a new blob file. The file is closed and removed automatically when |
| // the test finishes. |
| void CreateBlobFile() { |
| // blob_fd_closer_ takes a pointer to blob_fd_. Make sure we destroy a |
| // previous instance before overriding blob_fd_. |
| blob_fd_closer_.reset(); |
| EXPECT_TRUE(utils::MakeTempFile( |
| "InplaceGenerator_blob_file.XXXXXX", &blob_path_, &blob_fd_)); |
| blob_path_unlinker_.reset(new ScopedPathUnlinker(blob_path_)); |
| blob_fd_closer_.reset(new ScopedFdCloser(&blob_fd_)); |
| blob_file_size_ = 0; |
| EXPECT_GE(blob_fd_, 0); |
| blob_file_.reset(new BlobFileWriter(blob_fd_, &blob_file_size_)); |
| } |
| |
| // Dump the list of operations |aops| in case of test failure. |
| void DumpAopsOnFailure(const vector<AnnotatedOperation>& aops) { |
| if (HasNonfatalFailure()) { |
| LOG(INFO) << "Result operation list:"; |
| for (const auto& aop : aops) { |
| LOG(INFO) << aop; |
| } |
| } |
| } |
| |
| // Blob file name, file descriptor and file size used to store operation |
| // blobs. |
| string blob_path_; |
| int blob_fd_{-1}; |
| off_t blob_file_size_{0}; |
| std::unique_ptr<BlobFileWriter> blob_file_; |
| std::unique_ptr<ScopedPathUnlinker> blob_path_unlinker_; |
| std::unique_ptr<ScopedFdCloser> blob_fd_closer_; |
| }; |
| |
| TEST_F(InplaceGeneratorTest, BlockDefaultValues) { |
| // Tests that a Block is initialized with the default values as a |
| // Vertex::kInvalidIndex. This is required by the delta generators. |
| Block block; |
| EXPECT_EQ(Vertex::kInvalidIndex, block.reader); |
| EXPECT_EQ(Vertex::kInvalidIndex, block.writer); |
| } |
| |
| TEST_F(InplaceGeneratorTest, SubstituteBlocksTest) { |
| vector<Extent> remove_blocks; |
| AppendExtent(&remove_blocks, 3, 3); |
| AppendExtent(&remove_blocks, 7, 1); |
| vector<Extent> replace_blocks; |
| AppendExtent(&replace_blocks, 10, 2); |
| AppendExtent(&replace_blocks, 13, 2); |
| Vertex vertex; |
| InstallOperation& op = vertex.aop.op; |
| OpAppendExtent(&op, 4, 3); |
| OpAppendExtent(&op, kSparseHole, 4); // Sparse hole in file |
| OpAppendExtent(&op, 3, 1); |
| OpAppendExtent(&op, 7, 3); |
| |
| InplaceGenerator::SubstituteBlocks(&vertex, remove_blocks, replace_blocks); |
| |
| EXPECT_EQ(7, op.src_extents_size()); |
| EXPECT_EQ(11U, op.src_extents(0).start_block()); |
| EXPECT_EQ(1U, op.src_extents(0).num_blocks()); |
| EXPECT_EQ(13U, op.src_extents(1).start_block()); |
| EXPECT_EQ(1U, op.src_extents(1).num_blocks()); |
| EXPECT_EQ(6U, op.src_extents(2).start_block()); |
| EXPECT_EQ(1U, op.src_extents(2).num_blocks()); |
| EXPECT_EQ(kSparseHole, op.src_extents(3).start_block()); |
| EXPECT_EQ(4U, op.src_extents(3).num_blocks()); |
| EXPECT_EQ(10U, op.src_extents(4).start_block()); |
| EXPECT_EQ(1U, op.src_extents(4).num_blocks()); |
| EXPECT_EQ(14U, op.src_extents(5).start_block()); |
| EXPECT_EQ(1U, op.src_extents(5).num_blocks()); |
| EXPECT_EQ(8U, op.src_extents(6).start_block()); |
| EXPECT_EQ(2U, op.src_extents(6).num_blocks()); |
| } |
| |
| TEST_F(InplaceGeneratorTest, CutEdgesTest) { |
| Graph graph; |
| vector<Block> blocks(9); |
| |
| // Create nodes in graph |
| { |
| graph.resize(graph.size() + 1); |
| graph.back().aop.op.set_type(InstallOperation::MOVE); |
| // Reads from blocks 3, 5, 7 |
| vector<Extent> extents; |
| AppendBlockToExtents(&extents, 3); |
| AppendBlockToExtents(&extents, 5); |
| AppendBlockToExtents(&extents, 7); |
| StoreExtents(extents, graph.back().aop.op.mutable_src_extents()); |
| blocks[3].reader = graph.size() - 1; |
| blocks[5].reader = graph.size() - 1; |
| blocks[7].reader = graph.size() - 1; |
| |
| // Writes to blocks 1, 2, 4 |
| extents.clear(); |
| AppendBlockToExtents(&extents, 1); |
| AppendBlockToExtents(&extents, 2); |
| AppendBlockToExtents(&extents, 4); |
| StoreExtents(extents, graph.back().aop.op.mutable_dst_extents()); |
| blocks[1].writer = graph.size() - 1; |
| blocks[2].writer = graph.size() - 1; |
| blocks[4].writer = graph.size() - 1; |
| } |
| { |
| graph.resize(graph.size() + 1); |
| graph.back().aop.op.set_type(InstallOperation::MOVE); |
| // Reads from blocks 1, 2, 4 |
| vector<Extent> extents; |
| AppendBlockToExtents(&extents, 1); |
| AppendBlockToExtents(&extents, 2); |
| AppendBlockToExtents(&extents, 4); |
| StoreExtents(extents, graph.back().aop.op.mutable_src_extents()); |
| blocks[1].reader = graph.size() - 1; |
| blocks[2].reader = graph.size() - 1; |
| blocks[4].reader = graph.size() - 1; |
| |
| // Writes to blocks 3, 5, 6 |
| extents.clear(); |
| AppendBlockToExtents(&extents, 3); |
| AppendBlockToExtents(&extents, 5); |
| AppendBlockToExtents(&extents, 6); |
| StoreExtents(extents, graph.back().aop.op.mutable_dst_extents()); |
| blocks[3].writer = graph.size() - 1; |
| blocks[5].writer = graph.size() - 1; |
| blocks[6].writer = graph.size() - 1; |
| } |
| |
| // Create edges |
| InplaceGenerator::CreateEdges(&graph, blocks); |
| |
| // Find cycles |
| CycleBreaker cycle_breaker; |
| set<Edge> cut_edges; |
| cycle_breaker.BreakCycles(graph, &cut_edges); |
| |
| EXPECT_EQ(1U, cut_edges.size()); |
| EXPECT_TRUE(cut_edges.end() != cut_edges.find( |
| std::pair<Vertex::Index, Vertex::Index>(1, 0))); |
| |
| vector<CutEdgeVertexes> cuts; |
| EXPECT_TRUE(InplaceGenerator::CutEdges(&graph, cut_edges, &cuts)); |
| |
| EXPECT_EQ(3U, graph.size()); |
| |
| // Check new node in graph: |
| EXPECT_EQ(InstallOperation::MOVE, graph.back().aop.op.type()); |
| EXPECT_EQ(2, graph.back().aop.op.src_extents_size()); |
| EXPECT_EQ(1, graph.back().aop.op.dst_extents_size()); |
| EXPECT_EQ(kTempBlockStart, graph.back().aop.op.dst_extents(0).start_block()); |
| EXPECT_EQ(2U, graph.back().aop.op.dst_extents(0).num_blocks()); |
| EXPECT_TRUE(graph.back().out_edges.empty()); |
| |
| // Check that old node reads from new blocks |
| EXPECT_EQ(2, graph[0].aop.op.src_extents_size()); |
| EXPECT_EQ(kTempBlockStart, graph[0].aop.op.src_extents(0).start_block()); |
| EXPECT_EQ(2U, graph[0].aop.op.src_extents(0).num_blocks()); |
| EXPECT_EQ(7U, graph[0].aop.op.src_extents(1).start_block()); |
| EXPECT_EQ(1U, graph[0].aop.op.src_extents(1).num_blocks()); |
| |
| // And that the old dst extents haven't changed |
| EXPECT_EQ(2, graph[0].aop.op.dst_extents_size()); |
| EXPECT_EQ(1U, graph[0].aop.op.dst_extents(0).start_block()); |
| EXPECT_EQ(2U, graph[0].aop.op.dst_extents(0).num_blocks()); |
| EXPECT_EQ(4U, graph[0].aop.op.dst_extents(1).start_block()); |
| EXPECT_EQ(1U, graph[0].aop.op.dst_extents(1).num_blocks()); |
| |
| // Ensure it only depends on the next node and the new temp node |
| EXPECT_EQ(2U, graph[0].out_edges.size()); |
| EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(1)); |
| EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(graph.size() - |
| 1)); |
| |
| // Check second node has unchanged extents |
| EXPECT_EQ(2, graph[1].aop.op.src_extents_size()); |
| EXPECT_EQ(1U, graph[1].aop.op.src_extents(0).start_block()); |
| EXPECT_EQ(2U, graph[1].aop.op.src_extents(0).num_blocks()); |
| EXPECT_EQ(4U, graph[1].aop.op.src_extents(1).start_block()); |
| EXPECT_EQ(1U, graph[1].aop.op.src_extents(1).num_blocks()); |
| |
| EXPECT_EQ(2, graph[1].aop.op.dst_extents_size()); |
| EXPECT_EQ(3U, graph[1].aop.op.dst_extents(0).start_block()); |
| EXPECT_EQ(1U, graph[1].aop.op.dst_extents(0).num_blocks()); |
| EXPECT_EQ(5U, graph[1].aop.op.dst_extents(1).start_block()); |
| EXPECT_EQ(2U, graph[1].aop.op.dst_extents(1).num_blocks()); |
| |
| // Ensure it only depends on the next node |
| EXPECT_EQ(1U, graph[1].out_edges.size()); |
| EXPECT_TRUE(graph[1].out_edges.end() != graph[1].out_edges.find(2)); |
| } |
| |
| TEST_F(InplaceGeneratorTest, AssignTempBlocksReuseTest) { |
| Graph graph(9); |
| |
| const vector<Extent> empt; |
| uint64_t tmp = kTempBlockStart; |
| const string kFilename = "/foo"; |
| |
| vector<CutEdgeVertexes> cuts; |
| cuts.resize(3); |
| |
| // Simple broken loop: |
| GenVertex( |
| &graph[0], VectOfExt(0, 1), VectOfExt(1, 1), "", InstallOperation::MOVE); |
| GenVertex(&graph[1], |
| VectOfExt(tmp, 1), |
| VectOfExt(0, 1), |
| "", |
| InstallOperation::MOVE); |
| GenVertex(&graph[2], |
| VectOfExt(1, 1), |
| VectOfExt(tmp, 1), |
| "", |
| InstallOperation::MOVE); |
| // Corresponding edges: |
| graph[0].out_edges[2] = EdgeWithReadDep(VectOfExt(1, 1)); |
| graph[1].out_edges[2] = EdgeWithWriteDep(VectOfExt(tmp, 1)); |
| graph[1].out_edges[0] = EdgeWithReadDep(VectOfExt(0, 1)); |
| // Store the cut: |
| cuts[0].old_dst = 1; |
| cuts[0].old_src = 0; |
| cuts[0].new_vertex = 2; |
| cuts[0].tmp_extents = VectOfExt(tmp, 1); |
| tmp++; |
| |
| // Slightly more complex pair of loops: |
| GenVertex( |
| &graph[3], VectOfExt(4, 2), VectOfExt(2, 2), "", InstallOperation::MOVE); |
| GenVertex( |
| &graph[4], VectOfExt(6, 1), VectOfExt(7, 1), "", InstallOperation::MOVE); |
| GenVertex(&graph[5], |
| VectOfExt(tmp, 3), |
| VectOfExt(4, 3), |
| kFilename, |
| InstallOperation::MOVE); |
| GenVertex(&graph[6], |
| VectOfExt(2, 2), |
| VectOfExt(tmp, 2), |
| "", |
| InstallOperation::MOVE); |
| GenVertex(&graph[7], |
| VectOfExt(7, 1), |
| VectOfExt(tmp + 2, 1), |
| "", |
| InstallOperation::MOVE); |
| // Corresponding edges: |
| graph[3].out_edges[6] = EdgeWithReadDep(VectOfExt(2, 2)); |
| graph[4].out_edges[7] = EdgeWithReadDep(VectOfExt(7, 1)); |
| graph[5].out_edges[6] = EdgeWithWriteDep(VectOfExt(tmp, 2)); |
| graph[5].out_edges[7] = EdgeWithWriteDep(VectOfExt(tmp + 2, 1)); |
| graph[5].out_edges[3] = EdgeWithReadDep(VectOfExt(4, 2)); |
| graph[5].out_edges[4] = EdgeWithReadDep(VectOfExt(6, 1)); |
| // Store the cuts: |
| cuts[1].old_dst = 5; |
| cuts[1].old_src = 3; |
| cuts[1].new_vertex = 6; |
| cuts[1].tmp_extents = VectOfExt(tmp, 2); |
| cuts[2].old_dst = 5; |
| cuts[2].old_src = 4; |
| cuts[2].new_vertex = 7; |
| cuts[2].tmp_extents = VectOfExt(tmp + 2, 1); |
| |
| // Supplier of temp block: |
| GenVertex(&graph[8], empt, VectOfExt(8, 1), "", InstallOperation::REPLACE); |
| |
| // Specify the final order: |
| vector<Vertex::Index> op_indexes; |
| op_indexes.push_back(2); |
| op_indexes.push_back(0); |
| op_indexes.push_back(1); |
| op_indexes.push_back(6); |
| op_indexes.push_back(3); |
| op_indexes.push_back(7); |
| op_indexes.push_back(4); |
| op_indexes.push_back(5); |
| op_indexes.push_back(8); |
| |
| vector<vector<Vertex::Index>::size_type> reverse_op_indexes; |
| InplaceGenerator::GenerateReverseTopoOrderMap(op_indexes, |
| &reverse_op_indexes); |
| |
| CreateBlobFile(); |
| EXPECT_TRUE(InplaceGenerator::AssignTempBlocks(&graph, |
| "/dev/zero", |
| blob_file_.get(), |
| &op_indexes, |
| &reverse_op_indexes, |
| cuts)); |
| EXPECT_FALSE(graph[6].valid); |
| EXPECT_FALSE(graph[7].valid); |
| EXPECT_EQ(1, graph[1].aop.op.src_extents_size()); |
| EXPECT_EQ(2U, graph[1].aop.op.src_extents(0).start_block()); |
| EXPECT_EQ(1U, graph[1].aop.op.src_extents(0).num_blocks()); |
| EXPECT_EQ(InstallOperation::REPLACE_BZ, graph[5].aop.op.type()); |
| } |
| |
| TEST_F(InplaceGeneratorTest, MoveAndSortFullOpsToBackTest) { |
| Graph graph(4); |
| graph[0].aop.name = "A"; |
| graph[0].aop.op.set_type(InstallOperation::REPLACE); |
| graph[1].aop.name = "B"; |
| graph[1].aop.op.set_type(InstallOperation::BSDIFF); |
| graph[2].aop.name = "C"; |
| graph[2].aop.op.set_type(InstallOperation::REPLACE_BZ); |
| graph[3].aop.name = "D"; |
| graph[3].aop.op.set_type(InstallOperation::MOVE); |
| |
| vector<Vertex::Index> vect(graph.size()); |
| |
| for (vector<Vertex::Index>::size_type i = 0; i < vect.size(); ++i) { |
| vect[i] = i; |
| } |
| InplaceGenerator::MoveAndSortFullOpsToBack(&graph, &vect); |
| EXPECT_EQ(vect.size(), graph.size()); |
| EXPECT_EQ(graph[vect[0]].aop.name, "B"); |
| EXPECT_EQ(graph[vect[1]].aop.name, "D"); |
| EXPECT_EQ(graph[vect[2]].aop.name, "A"); |
| EXPECT_EQ(graph[vect[3]].aop.name, "C"); |
| } |
| |
| TEST_F(InplaceGeneratorTest, AssignTempBlocksTest) { |
| Graph graph(9); |
| const vector<Extent> empt; // empty |
| const string kFilename = "/foo"; |
| |
| // Some scratch space: |
| GenVertex(&graph[0], empt, VectOfExt(200, 1), "", InstallOperation::REPLACE); |
| GenVertex(&graph[1], empt, VectOfExt(210, 10), "", InstallOperation::REPLACE); |
| GenVertex(&graph[2], empt, VectOfExt(220, 1), "", InstallOperation::REPLACE); |
| |
| // A cycle that requires 10 blocks to break: |
| GenVertex(&graph[3], |
| VectOfExt(10, 11), |
| VectOfExt(0, 9), |
| "", |
| InstallOperation::BSDIFF); |
| graph[3].out_edges[4] = EdgeWithReadDep(VectOfExt(0, 9)); |
| GenVertex(&graph[4], |
| VectOfExt(0, 9), |
| VectOfExt(10, 11), |
| "", |
| InstallOperation::BSDIFF); |
| graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11)); |
| |
| // A cycle that requires 9 blocks to break: |
| GenVertex(&graph[5], |
| VectOfExt(40, 11), |
| VectOfExt(30, 10), |
| "", |
| InstallOperation::BSDIFF); |
| graph[5].out_edges[6] = EdgeWithReadDep(VectOfExt(30, 10)); |
| GenVertex(&graph[6], |
| VectOfExt(30, 10), |
| VectOfExt(40, 11), |
| "", |
| InstallOperation::BSDIFF); |
| graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11)); |
| |
| // A cycle that requires 40 blocks to break (which is too many): |
| GenVertex(&graph[7], |
| VectOfExt(120, 50), |
| VectOfExt(60, 40), |
| "", |
| InstallOperation::BSDIFF); |
| graph[7].out_edges[8] = EdgeWithReadDep(VectOfExt(60, 40)); |
| GenVertex(&graph[8], |
| VectOfExt(60, 40), |
| VectOfExt(120, 50), |
| kFilename, |
| InstallOperation::BSDIFF); |
| graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50)); |
| |
| graph_utils::DumpGraph(graph); |
| |
| vector<Vertex::Index> final_order; |
| |
| CreateBlobFile(); |
| EXPECT_TRUE(InplaceGenerator::ConvertGraphToDag(&graph, |
| "/dev/zero", |
| blob_file_.get(), |
| &final_order, |
| Vertex::kInvalidIndex)); |
| |
| Graph expected_graph(12); |
| GenVertex(&expected_graph[0], |
| empt, |
| VectOfExt(200, 1), |
| "", |
| InstallOperation::REPLACE); |
| GenVertex(&expected_graph[1], |
| empt, |
| VectOfExt(210, 10), |
| "", |
| InstallOperation::REPLACE); |
| GenVertex(&expected_graph[2], |
| empt, |
| VectOfExt(220, 1), |
| "", |
| InstallOperation::REPLACE); |
| GenVertex(&expected_graph[3], |
| VectOfExt(10, 11), |
| VectOfExt(0, 9), |
| "", |
| InstallOperation::BSDIFF); |
| expected_graph[3].out_edges[9] = EdgeWithReadDep(VectOfExt(0, 9)); |
| GenVertex(&expected_graph[4], |
| VectOfExt(60, 9), |
| VectOfExt(10, 11), |
| "", |
| InstallOperation::BSDIFF); |
| expected_graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11)); |
| expected_graph[4].out_edges[9] = EdgeWithWriteDep(VectOfExt(60, 9)); |
| GenVertex(&expected_graph[5], |
| VectOfExt(40, 11), |
| VectOfExt(30, 10), |
| "", |
| InstallOperation::BSDIFF); |
| expected_graph[5].out_edges[10] = EdgeWithReadDep(VectOfExt(30, 10)); |
| |
| GenVertex(&expected_graph[6], |
| VectOfExt(60, 10), |
| VectOfExt(40, 11), |
| "", |
| InstallOperation::BSDIFF); |
| expected_graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11)); |
| expected_graph[6].out_edges[10] = EdgeWithWriteDep(VectOfExt(60, 10)); |
| |
| GenVertex(&expected_graph[7], |
| VectOfExt(120, 50), |
| VectOfExt(60, 40), |
| "", |
| InstallOperation::BSDIFF); |
| expected_graph[7].out_edges[6] = EdgeWithReadDep(VectOfExt(60, 10)); |
| |
| GenVertex(&expected_graph[8], |
| empt, |
| VectOfExt(0, 50), |
| "/foo", |
| InstallOperation::REPLACE_BZ); |
| expected_graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50)); |
| |
| GenVertex(&expected_graph[9], |
| VectOfExt(0, 9), |
| VectOfExt(60, 9), |
| "", |
| InstallOperation::MOVE); |
| |
| GenVertex(&expected_graph[10], |
| VectOfExt(30, 10), |
| VectOfExt(60, 10), |
| "", |
| InstallOperation::MOVE); |
| expected_graph[10].out_edges[4] = EdgeWithReadDep(VectOfExt(60, 9)); |
| |
| EXPECT_EQ(12U, graph.size()); |
| EXPECT_FALSE(graph.back().valid); |
| for (Graph::size_type i = 0; i < graph.size() - 1; i++) { |
| EXPECT_TRUE(graph[i].out_edges == expected_graph[i].out_edges); |
| if (i == 8) { |
| // special case |
| } else { |
| // EXPECT_TRUE(graph[i] == expected_graph[i]) << "i = " << i; |
| } |
| } |
| } |
| |
| TEST_F(InplaceGeneratorTest, CreateScratchNodeTest) { |
| Vertex vertex; |
| InplaceGenerator::CreateScratchNode(12, 34, &vertex); |
| EXPECT_EQ(InstallOperation::REPLACE_BZ, vertex.aop.op.type()); |
| EXPECT_EQ(0U, vertex.aop.op.data_offset()); |
| EXPECT_EQ(0U, vertex.aop.op.data_length()); |
| EXPECT_EQ(1, vertex.aop.op.dst_extents_size()); |
| EXPECT_EQ(12U, vertex.aop.op.dst_extents(0).start_block()); |
| EXPECT_EQ(34U, vertex.aop.op.dst_extents(0).num_blocks()); |
| } |
| |
| TEST_F(InplaceGeneratorTest, ApplyMapTest) { |
| vector<uint64_t> collection = {1, 2, 3, 4, 6}; |
| vector<uint64_t> expected_values = {1, 2, 5, 4, 8}; |
| map<uint64_t, uint64_t> value_map; |
| value_map[3] = 5; |
| value_map[6] = 8; |
| value_map[5] = 10; |
| |
| InplaceGenerator::ApplyMap(&collection, value_map); |
| EXPECT_EQ(expected_values, collection); |
| } |
| |
| // We can't produce MOVE operations with a source or destination in the block 0. |
| // This test checks that the cycle breaker procedure doesn't produce such |
| // operations. |
| TEST_F(InplaceGeneratorTest, ResolveReadAfterWriteDependenciesAvoidMoveToZero) { |
| size_t block_size = 4096; |
| size_t num_blocks = 4; |
| vector<AnnotatedOperation> aops; |
| |
| // Create a REPLACE_BZ for block 0, and a circular dependency among all other |
| // blocks. This situation would prefer to issue a MOVE to scratch space and |
| // the only available block is 0. |
| aops.emplace_back(); |
| aops.back().name = base::StringPrintf("<bz-block-0>"); |
| aops.back().op.set_type(InstallOperation::REPLACE_BZ); |
| StoreExtents({ExtentForRange(0, 1)}, aops.back().op.mutable_dst_extents()); |
| |
| for (size_t i = 1; i < num_blocks; i++) { |
| AnnotatedOperation aop; |
| aop.name = base::StringPrintf("<op-%" PRIuS ">", i); |
| aop.op.set_type(InstallOperation::BSDIFF); |
| StoreExtents({ExtentForRange(1 + i % (num_blocks - 1), 1)}, |
| aop.op.mutable_src_extents()); |
| StoreExtents({ExtentForRange(i, 1)}, aop.op.mutable_dst_extents()); |
| aops.push_back(aop); |
| } |
| |
| PartitionConfig part("part"); |
| part.path = "/dev/zero"; |
| part.size = num_blocks * block_size; |
| |
| CreateBlobFile(); |
| |
| // We ran two tests here. The first one without enough blocks for the scratch |
| // space, forcing it to create a new full operation and the second case with |
| // one extra block in the partition that can be used for the move operation. |
| for (const auto part_blocks : vector<uint64_t>{num_blocks, num_blocks + 1}) { |
| SCOPED_TRACE( |
| base::StringPrintf("Using partition_blocks=%" PRIu64, part_blocks)); |
| vector<AnnotatedOperation> result_aops = aops; |
| EXPECT_TRUE(InplaceGenerator::ResolveReadAfterWriteDependencies( |
| part, |
| part, |
| part_blocks * block_size, |
| block_size, |
| blob_file_.get(), |
| &result_aops)); |
| |
| size_t full_ops = 0; |
| for (const auto& aop : result_aops) { |
| if (diff_utils::IsAReplaceOperation(aop.op.type())) |
| full_ops++; |
| |
| if (aop.op.type() != InstallOperation::MOVE) |
| continue; |
| for (const Extent& extent : aop.op.src_extents()) { |
| EXPECT_NE(0U, extent.start_block()) |
| << "On src extents for aop: " << aop; |
| } |
| for (const Extent& extent : aop.op.dst_extents()) { |
| EXPECT_NE(0U, extent.start_block()) |
| << "On dst extents for aop: " << aop; |
| } |
| } |
| |
| // If there's extra space in the partition, it should not use a new full |
| // operation for it. |
| EXPECT_EQ(part_blocks == num_blocks ? 2U : 1U, full_ops); |
| |
| DumpAopsOnFailure(result_aops); |
| } |
| } |
| |
| // Test that we can shrink a filesystem and break cycles. |
| TEST_F(InplaceGeneratorTest, ResolveReadAfterWriteDependenciesShrinkData) { |
| size_t block_size = 4096; |
| size_t old_blocks = 10; |
| size_t new_blocks = 8; |
| vector<AnnotatedOperation> aops; |
| |
| // Create a loop using the blocks 1-6 and one other operation writing to the |
| // block 7 from outside the new partition. The loop in the blocks 1-6 uses |
| // two-block operations, so it needs two blocks of scratch space. It can't use |
| // the block 0 as scratch space (see previous test) and it can't use the |
| // blocks 7 or 8 due the last move operation. |
| |
| aops.emplace_back(); |
| aops.back().name = base::StringPrintf("<bz-block-0>"); |
| aops.back().op.set_type(InstallOperation::REPLACE_BZ); |
| StoreExtents({ExtentForRange(0, 1)}, aops.back().op.mutable_dst_extents()); |
| |
| const size_t num_ops = 3; |
| for (size_t i = 0; i < num_ops; i++) { |
| AnnotatedOperation aop; |
| aop.name = base::StringPrintf("<op-%" PRIuS ">", i); |
| aop.op.set_type(InstallOperation::BSDIFF); |
| StoreExtents({ExtentForRange(1 + 2 * i, 2)}, aop.op.mutable_src_extents()); |
| StoreExtents({ExtentForRange(1 + 2 * ((i + 1) % num_ops), 2)}, |
| aop.op.mutable_dst_extents()); |
| aops.push_back(aop); |
| } |
| |
| { |
| AnnotatedOperation aop; |
| aop.name = "<op-shrink>"; |
| aop.op.set_type(InstallOperation::BSDIFF); |
| StoreExtents({ExtentForRange(8, 1)}, aop.op.mutable_src_extents()); |
| StoreExtents({ExtentForRange(7, 1)}, aop.op.mutable_dst_extents()); |
| aops.push_back(aop); |
| } |
| |
| PartitionConfig old_part("part"); |
| old_part.path = "/dev/zero"; |
| old_part.size = old_blocks * block_size; |
| |
| PartitionConfig new_part("part"); |
| new_part.path = "/dev/zero"; |
| new_part.size = new_blocks * block_size; |
| |
| CreateBlobFile(); |
| |
| EXPECT_TRUE(InplaceGenerator::ResolveReadAfterWriteDependencies( |
| old_part, |
| new_part, |
| (old_blocks + 2) * block_size, // enough scratch space. |
| block_size, |
| blob_file_.get(), |
| &aops)); |
| |
| size_t full_ops = 0; |
| for (const auto& aop : aops) { |
| if (diff_utils::IsAReplaceOperation(aop.op.type())) |
| full_ops++; |
| } |
| // There should be only one REPLACE* operation, the one we added for block 0. |
| EXPECT_EQ(1U, full_ops); |
| |
| // There should be only one MOVE operation, the one used to break the loop |
| // which should write to scratch space past the block 7 (the last block of the |
| // new partition) which is being written later. |
| size_t move_ops = 0; |
| for (const auto& aop : aops) { |
| if (aop.op.type() == InstallOperation::MOVE) { |
| move_ops++; |
| for (const Extent& extent : aop.op.dst_extents()) { |
| EXPECT_LE(7U, extent.start_block()) << "On dst extents for aop: " |
| << aop; |
| } |
| } |
| } |
| EXPECT_EQ(1U, move_ops); |
| |
| DumpAopsOnFailure(aops); |
| } |
| |
| } // namespace chromeos_update_engine |