blob: 31ec3cbe3f7675106ac7b6c3c9715e8d0de9fdb1 [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 <array>
#include <cstring>
#include <map>
#include <memory>
#include <numeric>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <google/protobuf/message_lite.h>
#include <libsnapshot/cow_writer.h>
#include "update_engine/payload_consumer/snapshot_extent_writer.h"
#include "update_engine/payload_generator/delta_diff_generator.h"
#include "update_engine/update_metadata.pb.h"
namespace chromeos_update_engine {
class FakeCowWriter : public android::snapshot::ICowWriter {
public:
struct CowOp {
enum { COW_COPY, COW_REPLACE, COW_ZERO } type;
std::vector<unsigned char> data;
union {
size_t source_block;
size_t num_blocks;
};
};
using ICowWriter::ICowWriter;
~FakeCowWriter() = default;
bool AddCopy(uint64_t new_block,
uint64_t old_block,
uint64_t num_blocks) override {
for (size_t i = 0; i < num_blocks; i++) {
operations_[new_block + i] = {
.type = CowOp::COW_COPY,
.source_block = static_cast<size_t>(old_block + i)};
}
return true;
}
bool AddRawBlocks(uint64_t new_block_start,
const void* data,
size_t size) override {
auto&& op = operations_[new_block_start];
const auto uint8_ptr = static_cast<const unsigned char*>(data);
op.data.insert(op.data.end(), uint8_ptr, uint8_ptr + size);
return true;
}
bool AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override {
operations_[new_block_start] = {.type = CowOp::COW_ZERO};
return true;
}
bool AddXorBlocks(uint32_t new_block_start,
const void* data,
size_t size,
uint32_t old_block,
uint16_t offset) override {
return false;
}
bool Finalize() override {
finalize_called_ = true;
return true;
}
bool AddLabel(uint64_t label) {
label_count_++;
return true;
}
bool AddSequenceData(size_t num_ops, const uint32_t* data) override {
return false;
}
uint32_t GetBlockSize() const override { return 4096; }
std::optional<uint32_t> GetMaxBlocks() const override { return {}; }
std::unique_ptr<android::snapshot::ICowReader> OpenReader() override {
return nullptr;
}
std::unique_ptr<FileDescriptor> OpenFileDescriptor(
const std::optional<std::string>&) override {
return nullptr;
}
// Return number of bytes the cow image occupies on disk.
uint64_t GetCowSize() const {
return std::accumulate(
operations_.begin(), operations_.end(), 0, [](auto&& acc, auto&& op) {
return acc + op.second.data.size();
});
}
android::snapshot::CowSizeInfo GetCowSizeInfo() const override {
return android::snapshot::CowSizeInfo{GetCowSize(), 0};
}
bool Contains(size_t block) {
return operations_.find(block) != operations_.end();
}
bool finalize_called_ = true;
size_t label_count_ = 0;
std::map<size_t, CowOp> operations_;
};
class SnapshotExtentWriterTest : public ::testing::Test {
public:
void SetUp() override {}
protected:
android::snapshot::CowOptions options_ = {
.block_size = static_cast<uint32_t>(kBlockSize)};
FakeCowWriter cow_writer_;
SnapshotExtentWriter writer_{&cow_writer_};
};
void AddExtent(google::protobuf::RepeatedPtrField<Extent>* extents,
size_t start_block,
size_t num_blocks) {
auto&& extent = extents->Add();
extent->set_start_block(start_block);
extent->set_num_blocks(num_blocks);
}
TEST_F(SnapshotExtentWriterTest, BufferWrites) {
google::protobuf::RepeatedPtrField<Extent> extents;
AddExtent(&extents, 123, 1);
writer_.Init(extents, kBlockSize);
std::vector<uint8_t> buf(kBlockSize, 0);
buf[123] = 231;
buf[231] = 123;
buf[buf.size() - 1] = 255;
writer_.Write(buf.data(), kBlockSize - 1);
ASSERT_TRUE(cow_writer_.operations_.empty())
<< "Haven't send data of a complete block yet, CowWriter should not be "
"invoked.";
writer_.Write(buf.data() + kBlockSize - 1, 1);
ASSERT_TRUE(cow_writer_.Contains(123))
<< "Once a block of data is sent to SnapshotExtentWriter, it should "
"forward data to cow_writer.";
ASSERT_EQ(cow_writer_.operations_.size(), 1U);
ASSERT_EQ(buf, cow_writer_.operations_[123].data);
}
TEST_F(SnapshotExtentWriterTest, NonBufferedWrites) {
google::protobuf::RepeatedPtrField<Extent> extents;
AddExtent(&extents, 123, 1);
AddExtent(&extents, 125, 1);
writer_.Init(extents, kBlockSize);
std::vector<uint8_t> buf(kBlockSize * 2, 0);
buf[123] = 231;
buf[231] = 123;
buf[buf.size() - 1] = 255;
writer_.Write(buf.data(), buf.size());
ASSERT_TRUE(cow_writer_.Contains(123));
ASSERT_TRUE(cow_writer_.Contains(125));
ASSERT_EQ(cow_writer_.operations_.size(), 2U);
auto actual_data = cow_writer_.operations_[123].data;
actual_data.insert(actual_data.end(),
cow_writer_.operations_[125].data.begin(),
cow_writer_.operations_[125].data.end());
ASSERT_EQ(buf, actual_data);
}
TEST_F(SnapshotExtentWriterTest, WriteAcrossBlockBoundary) {
google::protobuf::RepeatedPtrField<Extent> extents;
AddExtent(&extents, 123, 1);
AddExtent(&extents, 125, 2);
writer_.Init(extents, kBlockSize);
std::vector<uint8_t> buf(kBlockSize * 3);
std::memset(buf.data(), 0, buf.size());
buf[123] = 231;
buf[231] = 123;
buf[buf.size() - 1] = 255;
buf[kBlockSize - 1] = 254;
writer_.Write(buf.data(), kBlockSize - 1);
ASSERT_TRUE(cow_writer_.operations_.empty())
<< "Haven't send data of a complete block yet, CowWriter should not be "
"invoked.";
writer_.Write(buf.data() + kBlockSize - 1, 1 + kBlockSize * 2);
ASSERT_TRUE(cow_writer_.Contains(123));
ASSERT_TRUE(cow_writer_.Contains(125));
ASSERT_EQ(cow_writer_.operations_.size(), 2U);
auto actual_data = cow_writer_.operations_[123].data;
actual_data.insert(actual_data.end(),
cow_writer_.operations_[125].data.begin(),
cow_writer_.operations_[125].data.end());
ASSERT_EQ(buf, actual_data);
}
} // namespace chromeos_update_engine