//
|
// Copyright (C) 2018 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/boot_control_android.h"
|
|
#include <set>
|
#include <vector>
|
|
#include <base/logging.h>
|
#include <base/strings/string_util.h>
|
#include <fs_mgr.h>
|
#include <gmock/gmock.h>
|
#include <gtest/gtest.h>
|
#include <libdm/dm.h>
|
|
#include "update_engine/mock_boot_control_hal.h"
|
#include "update_engine/mock_dynamic_partition_control.h"
|
|
using android::dm::DmDeviceState;
|
using android::fs_mgr::MetadataBuilder;
|
using android::hardware::Void;
|
using std::string;
|
using testing::_;
|
using testing::AnyNumber;
|
using testing::Contains;
|
using testing::Eq;
|
using testing::Invoke;
|
using testing::Key;
|
using testing::MakeMatcher;
|
using testing::Matcher;
|
using testing::MatcherInterface;
|
using testing::MatchResultListener;
|
using testing::NiceMock;
|
using testing::Not;
|
using testing::Return;
|
|
namespace chromeos_update_engine {
|
|
constexpr const uint32_t kMaxNumSlots = 2;
|
constexpr const char* kSlotSuffixes[kMaxNumSlots] = {"_a", "_b"};
|
constexpr const char* kFakeDevicePath = "/fake/dev/path/";
|
constexpr const char* kFakeDmDevicePath = "/fake/dm/dev/path/";
|
constexpr const uint32_t kFakeMetadataSize = 65536;
|
constexpr const char* kDefaultGroup = "foo";
|
|
// A map describing the size of each partition.
|
// "{name, size}"
|
using PartitionSizes = std::map<string, uint64_t>;
|
|
// "{name_a, size}"
|
using PartitionSuffixSizes = std::map<string, uint64_t>;
|
|
using PartitionMetadata = BootControlInterface::PartitionMetadata;
|
|
// C++ standards do not allow uint64_t (aka unsigned long) to be the parameter
|
// of user-defined literal operators.
|
constexpr unsigned long long operator"" _MiB(unsigned long long x) { // NOLINT
|
return x << 20;
|
}
|
constexpr unsigned long long operator"" _GiB(unsigned long long x) { // NOLINT
|
return x << 30;
|
}
|
|
constexpr uint64_t kDefaultGroupSize = 5_GiB;
|
// Super device size. 1 MiB for metadata.
|
constexpr uint64_t kDefaultSuperSize = kDefaultGroupSize * 2 + 1_MiB;
|
|
template <typename U, typename V>
|
std::ostream& operator<<(std::ostream& os, const std::map<U, V>& param) {
|
os << "{";
|
bool first = true;
|
for (const auto& pair : param) {
|
if (!first)
|
os << ", ";
|
os << pair.first << ":" << pair.second;
|
first = false;
|
}
|
return os << "}";
|
}
|
|
template <typename T>
|
std::ostream& operator<<(std::ostream& os, const std::vector<T>& param) {
|
os << "[";
|
bool first = true;
|
for (const auto& e : param) {
|
if (!first)
|
os << ", ";
|
os << e;
|
first = false;
|
}
|
return os << "]";
|
}
|
|
std::ostream& operator<<(std::ostream& os,
|
const PartitionMetadata::Partition& p) {
|
return os << "{" << p.name << ", " << p.size << "}";
|
}
|
|
std::ostream& operator<<(std::ostream& os, const PartitionMetadata::Group& g) {
|
return os << "{" << g.name << ", " << g.size << ", " << g.partitions << "}";
|
}
|
|
std::ostream& operator<<(std::ostream& os, const PartitionMetadata& m) {
|
return os << m.groups;
|
}
|
|
inline string GetDevice(const string& name) {
|
return kFakeDevicePath + name;
|
}
|
|
inline string GetDmDevice(const string& name) {
|
return kFakeDmDevicePath + name;
|
}
|
|
// TODO(elsk): fs_mgr_get_super_partition_name should be mocked.
|
inline string GetSuperDevice(uint32_t slot) {
|
return GetDevice(fs_mgr_get_super_partition_name(slot));
|
}
|
|
struct TestParam {
|
uint32_t source;
|
uint32_t target;
|
};
|
std::ostream& operator<<(std::ostream& os, const TestParam& param) {
|
return os << "{source: " << param.source << ", target:" << param.target
|
<< "}";
|
}
|
|
// To support legacy tests, auto-convert {name_a: size} map to
|
// PartitionMetadata.
|
PartitionMetadata partitionSuffixSizesToMetadata(
|
const PartitionSuffixSizes& partition_sizes) {
|
PartitionMetadata metadata;
|
for (const char* suffix : kSlotSuffixes) {
|
metadata.groups.push_back(
|
{string(kDefaultGroup) + suffix, kDefaultGroupSize, {}});
|
}
|
for (const auto& pair : partition_sizes) {
|
for (size_t suffix_idx = 0; suffix_idx < kMaxNumSlots; ++suffix_idx) {
|
if (base::EndsWith(pair.first,
|
kSlotSuffixes[suffix_idx],
|
base::CompareCase::SENSITIVE)) {
|
metadata.groups[suffix_idx].partitions.push_back(
|
{pair.first, pair.second});
|
}
|
}
|
}
|
return metadata;
|
}
|
|
// To support legacy tests, auto-convert {name: size} map to PartitionMetadata.
|
PartitionMetadata partitionSizesToMetadata(
|
const PartitionSizes& partition_sizes) {
|
PartitionMetadata metadata;
|
metadata.groups.push_back({string{kDefaultGroup}, kDefaultGroupSize, {}});
|
for (const auto& pair : partition_sizes) {
|
metadata.groups[0].partitions.push_back({pair.first, pair.second});
|
}
|
return metadata;
|
}
|
|
std::unique_ptr<MetadataBuilder> NewFakeMetadata(
|
const PartitionMetadata& metadata) {
|
auto builder =
|
MetadataBuilder::New(kDefaultSuperSize, kFakeMetadataSize, kMaxNumSlots);
|
EXPECT_GE(builder->AllocatableSpace(), kDefaultGroupSize * 2);
|
EXPECT_NE(nullptr, builder);
|
if (builder == nullptr)
|
return nullptr;
|
for (const auto& group : metadata.groups) {
|
EXPECT_TRUE(builder->AddGroup(group.name, group.size));
|
for (const auto& partition : group.partitions) {
|
auto p = builder->AddPartition(partition.name, group.name, 0 /* attr */);
|
EXPECT_TRUE(p && builder->ResizePartition(p, partition.size));
|
}
|
}
|
return builder;
|
}
|
|
class MetadataMatcher : public MatcherInterface<MetadataBuilder*> {
|
public:
|
explicit MetadataMatcher(const PartitionSuffixSizes& partition_sizes)
|
: partition_metadata_(partitionSuffixSizesToMetadata(partition_sizes)) {}
|
explicit MetadataMatcher(const PartitionMetadata& partition_metadata)
|
: partition_metadata_(partition_metadata) {}
|
|
bool MatchAndExplain(MetadataBuilder* metadata,
|
MatchResultListener* listener) const override {
|
bool success = true;
|
for (const auto& group : partition_metadata_.groups) {
|
for (const auto& partition : group.partitions) {
|
auto p = metadata->FindPartition(partition.name);
|
if (p == nullptr) {
|
if (!success)
|
*listener << "; ";
|
*listener << "No partition " << partition.name;
|
success = false;
|
continue;
|
}
|
if (p->size() != partition.size) {
|
if (!success)
|
*listener << "; ";
|
*listener << "Partition " << partition.name << " has size "
|
<< p->size() << ", expected " << partition.size;
|
success = false;
|
}
|
if (p->group_name() != group.name) {
|
if (!success)
|
*listener << "; ";
|
*listener << "Partition " << partition.name << " has group "
|
<< p->group_name() << ", expected " << group.name;
|
success = false;
|
}
|
}
|
}
|
return success;
|
}
|
|
void DescribeTo(std::ostream* os) const override {
|
*os << "expect: " << partition_metadata_;
|
}
|
|
void DescribeNegationTo(std::ostream* os) const override {
|
*os << "expect not: " << partition_metadata_;
|
}
|
|
private:
|
PartitionMetadata partition_metadata_;
|
};
|
|
inline Matcher<MetadataBuilder*> MetadataMatches(
|
const PartitionSuffixSizes& partition_sizes) {
|
return MakeMatcher(new MetadataMatcher(partition_sizes));
|
}
|
|
inline Matcher<MetadataBuilder*> MetadataMatches(
|
const PartitionMetadata& partition_metadata) {
|
return MakeMatcher(new MetadataMatcher(partition_metadata));
|
}
|
|
MATCHER_P(HasGroup, group, " has group " + group) {
|
auto groups = arg->ListGroups();
|
return std::find(groups.begin(), groups.end(), group) != groups.end();
|
}
|
|
class BootControlAndroidTest : public ::testing::Test {
|
protected:
|
void SetUp() override {
|
// Fake init bootctl_
|
bootctl_.module_ = new NiceMock<MockBootControlHal>();
|
bootctl_.dynamic_control_ =
|
std::make_unique<NiceMock<MockDynamicPartitionControl>>();
|
|
ON_CALL(module(), getNumberSlots()).WillByDefault(Invoke([] {
|
return kMaxNumSlots;
|
}));
|
ON_CALL(module(), getSuffix(_, _))
|
.WillByDefault(Invoke([](auto slot, auto cb) {
|
EXPECT_LE(slot, kMaxNumSlots);
|
cb(slot < kMaxNumSlots ? kSlotSuffixes[slot] : "");
|
return Void();
|
}));
|
|
ON_CALL(dynamicControl(), IsDynamicPartitionsEnabled())
|
.WillByDefault(Return(true));
|
ON_CALL(dynamicControl(), IsDynamicPartitionsRetrofit())
|
.WillByDefault(Return(false));
|
ON_CALL(dynamicControl(), DeviceExists(_)).WillByDefault(Return(true));
|
ON_CALL(dynamicControl(), GetDeviceDir(_))
|
.WillByDefault(Invoke([](auto path) {
|
*path = kFakeDevicePath;
|
return true;
|
}));
|
ON_CALL(dynamicControl(), GetDmDevicePathByName(_, _))
|
.WillByDefault(Invoke([](auto partition_name_suffix, auto device) {
|
*device = GetDmDevice(partition_name_suffix);
|
return true;
|
}));
|
}
|
|
// Return the mocked HAL module.
|
NiceMock<MockBootControlHal>& module() {
|
return static_cast<NiceMock<MockBootControlHal>&>(*bootctl_.module_);
|
}
|
|
// Return the mocked DynamicPartitionControlInterface.
|
NiceMock<MockDynamicPartitionControl>& dynamicControl() {
|
return static_cast<NiceMock<MockDynamicPartitionControl>&>(
|
*bootctl_.dynamic_control_);
|
}
|
|
// Set the fake metadata to return when LoadMetadataBuilder is called on
|
// |slot|.
|
void SetMetadata(uint32_t slot, const PartitionSuffixSizes& sizes) {
|
SetMetadata(slot, partitionSuffixSizesToMetadata(sizes));
|
}
|
|
void SetMetadata(uint32_t slot, const PartitionMetadata& metadata) {
|
EXPECT_CALL(dynamicControl(),
|
LoadMetadataBuilder(GetSuperDevice(slot), slot, _))
|
.Times(AnyNumber())
|
.WillRepeatedly(Invoke([metadata](auto, auto, auto) {
|
return NewFakeMetadata(metadata);
|
}));
|
}
|
|
// Expect that UnmapPartitionOnDeviceMapper is called on target() metadata
|
// slot with each partition in |partitions|.
|
void ExpectUnmap(const std::set<string>& partitions) {
|
// Error when UnmapPartitionOnDeviceMapper is called on unknown arguments.
|
ON_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_, _))
|
.WillByDefault(Return(false));
|
|
for (const auto& partition : partitions) {
|
EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(partition, _))
|
.WillOnce(Invoke([this](auto partition, auto) {
|
mapped_devices_.erase(partition);
|
return true;
|
}));
|
}
|
}
|
|
void ExpectDevicesAreMapped(const std::set<string>& partitions) {
|
ASSERT_EQ(partitions.size(), mapped_devices_.size());
|
for (const auto& partition : partitions) {
|
EXPECT_THAT(mapped_devices_, Contains(Key(Eq(partition))))
|
<< "Expect that " << partition << " is mapped, but it is not.";
|
}
|
}
|
|
void ExpectStoreMetadata(const PartitionSuffixSizes& partition_sizes) {
|
ExpectStoreMetadataMatch(MetadataMatches(partition_sizes));
|
}
|
|
virtual void ExpectStoreMetadataMatch(
|
const Matcher<MetadataBuilder*>& matcher) {
|
EXPECT_CALL(dynamicControl(),
|
StoreMetadata(GetSuperDevice(target()), matcher, target()))
|
.WillOnce(Return(true));
|
}
|
|
uint32_t source() { return slots_.source; }
|
|
uint32_t target() { return slots_.target; }
|
|
// Return partition names with suffix of source().
|
string S(const string& name) { return name + kSlotSuffixes[source()]; }
|
|
// Return partition names with suffix of target().
|
string T(const string& name) { return name + kSlotSuffixes[target()]; }
|
|
// Set source and target slots to use before testing.
|
void SetSlots(const TestParam& slots) {
|
slots_ = slots;
|
|
ON_CALL(module(), getCurrentSlot()).WillByDefault(Invoke([this] {
|
return source();
|
}));
|
// Should not store metadata to source slot.
|
EXPECT_CALL(dynamicControl(),
|
StoreMetadata(GetSuperDevice(source()), _, source()))
|
.Times(0);
|
// Should not load metadata from target slot.
|
EXPECT_CALL(dynamicControl(),
|
LoadMetadataBuilder(GetSuperDevice(target()), target(), _))
|
.Times(0);
|
}
|
|
bool InitPartitionMetadata(uint32_t slot,
|
PartitionSizes partition_sizes,
|
bool update_metadata = true) {
|
auto m = partitionSizesToMetadata(partition_sizes);
|
LOG(INFO) << m;
|
return bootctl_.InitPartitionMetadata(slot, m, update_metadata);
|
}
|
|
BootControlAndroid bootctl_; // BootControlAndroid under test.
|
TestParam slots_;
|
// mapped devices through MapPartitionOnDeviceMapper.
|
std::map<string, string> mapped_devices_;
|
};
|
|
class BootControlAndroidTestP
|
: public BootControlAndroidTest,
|
public ::testing::WithParamInterface<TestParam> {
|
public:
|
void SetUp() override {
|
BootControlAndroidTest::SetUp();
|
SetSlots(GetParam());
|
}
|
};
|
|
// Test resize case. Grow if target metadata contains a partition with a size
|
// less than expected.
|
TEST_P(BootControlAndroidTestP, NeedGrowIfSizeNotMatchWhenResizing) {
|
SetMetadata(source(),
|
{{S("system"), 2_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 2_GiB},
|
{T("vendor"), 1_GiB}});
|
ExpectStoreMetadata({{S("system"), 2_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 3_GiB},
|
{T("vendor"), 1_GiB}});
|
ExpectUnmap({T("system"), T("vendor")});
|
|
EXPECT_TRUE(
|
InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 1_GiB}}));
|
}
|
|
// Test resize case. Shrink if target metadata contains a partition with a size
|
// greater than expected.
|
TEST_P(BootControlAndroidTestP, NeedShrinkIfSizeNotMatchWhenResizing) {
|
SetMetadata(source(),
|
{{S("system"), 2_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 2_GiB},
|
{T("vendor"), 1_GiB}});
|
ExpectStoreMetadata({{S("system"), 2_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 2_GiB},
|
{T("vendor"), 150_MiB}});
|
ExpectUnmap({T("system"), T("vendor")});
|
|
EXPECT_TRUE(InitPartitionMetadata(target(),
|
{{"system", 2_GiB}, {"vendor", 150_MiB}}));
|
}
|
|
// Test adding partitions on the first run.
|
TEST_P(BootControlAndroidTestP, AddPartitionToEmptyMetadata) {
|
SetMetadata(source(), PartitionSuffixSizes{});
|
ExpectStoreMetadata({{T("system"), 2_GiB}, {T("vendor"), 1_GiB}});
|
ExpectUnmap({T("system"), T("vendor")});
|
|
EXPECT_TRUE(
|
InitPartitionMetadata(target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}));
|
}
|
|
// Test subsequent add case.
|
TEST_P(BootControlAndroidTestP, AddAdditionalPartition) {
|
SetMetadata(source(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}});
|
ExpectStoreMetadata(
|
{{S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}});
|
ExpectUnmap({T("system"), T("vendor")});
|
|
EXPECT_TRUE(
|
InitPartitionMetadata(target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}));
|
}
|
|
// Test delete one partition.
|
TEST_P(BootControlAndroidTestP, DeletePartition) {
|
SetMetadata(source(),
|
{{S("system"), 2_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 2_GiB},
|
{T("vendor"), 1_GiB}});
|
// No T("vendor")
|
ExpectStoreMetadata(
|
{{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}});
|
ExpectUnmap({T("system")});
|
|
EXPECT_TRUE(InitPartitionMetadata(target(), {{"system", 2_GiB}}));
|
}
|
|
// Test delete all partitions.
|
TEST_P(BootControlAndroidTestP, DeleteAll) {
|
SetMetadata(source(),
|
{{S("system"), 2_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 2_GiB},
|
{T("vendor"), 1_GiB}});
|
ExpectStoreMetadata({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}});
|
|
EXPECT_TRUE(InitPartitionMetadata(target(), {}));
|
}
|
|
// Test corrupt source metadata case.
|
TEST_P(BootControlAndroidTestP, CorruptedSourceMetadata) {
|
EXPECT_CALL(dynamicControl(),
|
LoadMetadataBuilder(GetSuperDevice(source()), source(), _))
|
.WillOnce(Invoke([](auto, auto, auto) { return nullptr; }));
|
ExpectUnmap({T("system")});
|
|
EXPECT_FALSE(InitPartitionMetadata(target(), {{"system", 1_GiB}}))
|
<< "Should not be able to continue with corrupt source metadata";
|
}
|
|
// Test that InitPartitionMetadata fail if there is not enough space on the
|
// device.
|
TEST_P(BootControlAndroidTestP, NotEnoughSpace) {
|
SetMetadata(source(),
|
{{S("system"), 3_GiB},
|
{S("vendor"), 2_GiB},
|
{T("system"), 0},
|
{T("vendor"), 0}});
|
EXPECT_FALSE(
|
InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 3_GiB}}))
|
<< "Should not be able to fit 11GiB data into 10GiB space";
|
}
|
|
TEST_P(BootControlAndroidTestP, NotEnoughSpaceForSlot) {
|
SetMetadata(source(),
|
{{S("system"), 1_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 0},
|
{T("vendor"), 0}});
|
EXPECT_FALSE(
|
InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 3_GiB}}))
|
<< "Should not be able to grow over size of super / 2";
|
}
|
|
// Test applying retrofit update on a build with dynamic partitions enabled.
|
TEST_P(BootControlAndroidTestP,
|
ApplyRetrofitUpdateOnDynamicPartitionsEnabledBuild) {
|
SetMetadata(source(),
|
{{S("system"), 2_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 2_GiB},
|
{T("vendor"), 1_GiB}});
|
// Should not try to unmap any target partition.
|
EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_, _)).Times(0);
|
// Should not store metadata to target slot.
|
EXPECT_CALL(dynamicControl(),
|
StoreMetadata(GetSuperDevice(target()), _, target()))
|
.Times(0);
|
|
// Not calling through BootControlAndroidTest::InitPartitionMetadata(), since
|
// we don't want any default group in the PartitionMetadata.
|
EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), {}, true));
|
|
// Should use dynamic source partitions.
|
EXPECT_CALL(dynamicControl(), GetState(S("system")))
|
.Times(1)
|
.WillOnce(Return(DmDeviceState::ACTIVE));
|
string system_device;
|
EXPECT_TRUE(bootctl_.GetPartitionDevice("system", source(), &system_device));
|
EXPECT_EQ(GetDmDevice(S("system")), system_device);
|
|
// Should use static target partitions without querying dynamic control.
|
EXPECT_CALL(dynamicControl(), GetState(T("system"))).Times(0);
|
EXPECT_TRUE(bootctl_.GetPartitionDevice("system", target(), &system_device));
|
EXPECT_EQ(GetDevice(T("system")), system_device);
|
|
// Static partition "bar".
|
EXPECT_CALL(dynamicControl(), GetState(S("bar"))).Times(0);
|
std::string bar_device;
|
EXPECT_TRUE(bootctl_.GetPartitionDevice("bar", source(), &bar_device));
|
EXPECT_EQ(GetDevice(S("bar")), bar_device);
|
|
EXPECT_CALL(dynamicControl(), GetState(T("bar"))).Times(0);
|
EXPECT_TRUE(bootctl_.GetPartitionDevice("bar", target(), &bar_device));
|
EXPECT_EQ(GetDevice(T("bar")), bar_device);
|
}
|
|
TEST_P(BootControlAndroidTestP, GetPartitionDeviceWhenResumingUpdate) {
|
// Both of the two slots contain valid partition metadata, since this is
|
// resuming an update.
|
SetMetadata(source(),
|
{{S("system"), 2_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 2_GiB},
|
{T("vendor"), 1_GiB}});
|
SetMetadata(target(),
|
{{S("system"), 2_GiB},
|
{S("vendor"), 1_GiB},
|
{T("system"), 2_GiB},
|
{T("vendor"), 1_GiB}});
|
EXPECT_CALL(dynamicControl(),
|
StoreMetadata(GetSuperDevice(target()), _, target()))
|
.Times(0);
|
EXPECT_TRUE(InitPartitionMetadata(
|
target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}, false));
|
|
// Dynamic partition "system".
|
EXPECT_CALL(dynamicControl(), GetState(S("system")))
|
.Times(1)
|
.WillOnce(Return(DmDeviceState::ACTIVE));
|
string system_device;
|
EXPECT_TRUE(bootctl_.GetPartitionDevice("system", source(), &system_device));
|
EXPECT_EQ(GetDmDevice(S("system")), system_device);
|
|
EXPECT_CALL(dynamicControl(), GetState(T("system")))
|
.Times(AnyNumber())
|
.WillOnce(Return(DmDeviceState::ACTIVE));
|
EXPECT_CALL(dynamicControl(),
|
MapPartitionOnDeviceMapper(
|
GetSuperDevice(target()), T("system"), target(), _, _))
|
.Times(AnyNumber())
|
.WillRepeatedly(
|
Invoke([](const auto&, const auto& name, auto, auto, auto* device) {
|
*device = "/fake/remapped/" + name;
|
return true;
|
}));
|
EXPECT_TRUE(bootctl_.GetPartitionDevice("system", target(), &system_device));
|
EXPECT_EQ("/fake/remapped/" + T("system"), system_device);
|
|
// Static partition "bar".
|
EXPECT_CALL(dynamicControl(), GetState(S("bar"))).Times(0);
|
std::string bar_device;
|
EXPECT_TRUE(bootctl_.GetPartitionDevice("bar", source(), &bar_device));
|
EXPECT_EQ(GetDevice(S("bar")), bar_device);
|
|
EXPECT_CALL(dynamicControl(), GetState(T("bar"))).Times(0);
|
EXPECT_TRUE(bootctl_.GetPartitionDevice("bar", target(), &bar_device));
|
EXPECT_EQ(GetDevice(T("bar")), bar_device);
|
}
|
|
INSTANTIATE_TEST_CASE_P(BootControlAndroidTest,
|
BootControlAndroidTestP,
|
testing::Values(TestParam{0, 1}, TestParam{1, 0}));
|
|
const PartitionSuffixSizes update_sizes_0() {
|
// Initial state is 0 for "other" slot.
|
return {
|
{"grown_a", 2_GiB},
|
{"shrunk_a", 1_GiB},
|
{"same_a", 100_MiB},
|
{"deleted_a", 150_MiB},
|
// no added_a
|
{"grown_b", 200_MiB},
|
// simulate system_other
|
{"shrunk_b", 0},
|
{"same_b", 0},
|
{"deleted_b", 0},
|
// no added_b
|
};
|
}
|
|
const PartitionSuffixSizes update_sizes_1() {
|
return {
|
{"grown_a", 2_GiB},
|
{"shrunk_a", 1_GiB},
|
{"same_a", 100_MiB},
|
{"deleted_a", 150_MiB},
|
// no added_a
|
{"grown_b", 3_GiB},
|
{"shrunk_b", 150_MiB},
|
{"same_b", 100_MiB},
|
{"added_b", 150_MiB},
|
// no deleted_b
|
};
|
}
|
|
const PartitionSuffixSizes update_sizes_2() {
|
return {
|
{"grown_a", 4_GiB},
|
{"shrunk_a", 100_MiB},
|
{"same_a", 100_MiB},
|
{"deleted_a", 64_MiB},
|
// no added_a
|
{"grown_b", 3_GiB},
|
{"shrunk_b", 150_MiB},
|
{"same_b", 100_MiB},
|
{"added_b", 150_MiB},
|
// no deleted_b
|
};
|
}
|
|
// Test case for first update after the device is manufactured, in which
|
// case the "other" slot is likely of size "0" (except system, which is
|
// non-zero because of system_other partition)
|
TEST_F(BootControlAndroidTest, SimulatedFirstUpdate) {
|
SetSlots({0, 1});
|
|
SetMetadata(source(), update_sizes_0());
|
SetMetadata(target(), update_sizes_0());
|
ExpectStoreMetadata(update_sizes_1());
|
ExpectUnmap({"grown_b", "shrunk_b", "same_b", "added_b"});
|
|
EXPECT_TRUE(InitPartitionMetadata(target(),
|
{{"grown", 3_GiB},
|
{"shrunk", 150_MiB},
|
{"same", 100_MiB},
|
{"added", 150_MiB}}));
|
}
|
|
// After first update, test for the second update. In the second update, the
|
// "added" partition is deleted and "deleted" partition is re-added.
|
TEST_F(BootControlAndroidTest, SimulatedSecondUpdate) {
|
SetSlots({1, 0});
|
|
SetMetadata(source(), update_sizes_1());
|
SetMetadata(target(), update_sizes_0());
|
|
ExpectStoreMetadata(update_sizes_2());
|
ExpectUnmap({"grown_a", "shrunk_a", "same_a", "deleted_a"});
|
|
EXPECT_TRUE(InitPartitionMetadata(target(),
|
{{"grown", 4_GiB},
|
{"shrunk", 100_MiB},
|
{"same", 100_MiB},
|
{"deleted", 64_MiB}}));
|
}
|
|
TEST_F(BootControlAndroidTest, ApplyingToCurrentSlot) {
|
SetSlots({1, 1});
|
EXPECT_FALSE(InitPartitionMetadata(target(), {}))
|
<< "Should not be able to apply to current slot.";
|
}
|
|
class BootControlAndroidGroupTestP : public BootControlAndroidTestP {
|
public:
|
void SetUp() override {
|
BootControlAndroidTestP::SetUp();
|
SetMetadata(
|
source(),
|
{.groups = {SimpleGroup(S("android"), 3_GiB, S("system"), 2_GiB),
|
SimpleGroup(S("oem"), 2_GiB, S("vendor"), 1_GiB),
|
SimpleGroup(T("android"), 3_GiB, T("system"), 0),
|
SimpleGroup(T("oem"), 2_GiB, T("vendor"), 0)}});
|
}
|
|
// Return a simple group with only one partition.
|
PartitionMetadata::Group SimpleGroup(const string& group,
|
uint64_t group_size,
|
const string& partition,
|
uint64_t partition_size) {
|
return {.name = group,
|
.size = group_size,
|
.partitions = {{.name = partition, .size = partition_size}}};
|
}
|
|
void ExpectStoreMetadata(const PartitionMetadata& partition_metadata) {
|
ExpectStoreMetadataMatch(MetadataMatches(partition_metadata));
|
}
|
|
// Expect that target slot is stored with target groups.
|
void ExpectStoreMetadataMatch(
|
const Matcher<MetadataBuilder*>& matcher) override {
|
BootControlAndroidTestP::ExpectStoreMetadataMatch(AllOf(
|
MetadataMatches(PartitionMetadata{
|
.groups = {SimpleGroup(S("android"), 3_GiB, S("system"), 2_GiB),
|
SimpleGroup(S("oem"), 2_GiB, S("vendor"), 1_GiB)}}),
|
matcher));
|
}
|
};
|
|
// Allow to resize within group.
|
TEST_P(BootControlAndroidGroupTestP, ResizeWithinGroup) {
|
ExpectStoreMetadata(PartitionMetadata{
|
.groups = {SimpleGroup(T("android"), 3_GiB, T("system"), 3_GiB),
|
SimpleGroup(T("oem"), 2_GiB, T("vendor"), 2_GiB)}});
|
ExpectUnmap({T("system"), T("vendor")});
|
|
EXPECT_TRUE(bootctl_.InitPartitionMetadata(
|
target(),
|
PartitionMetadata{
|
.groups = {SimpleGroup("android", 3_GiB, "system", 3_GiB),
|
SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}},
|
true));
|
}
|
|
TEST_P(BootControlAndroidGroupTestP, NotEnoughSpaceForGroup) {
|
EXPECT_FALSE(bootctl_.InitPartitionMetadata(
|
target(),
|
PartitionMetadata{
|
.groups = {SimpleGroup("android", 3_GiB, "system", 1_GiB),
|
SimpleGroup("oem", 2_GiB, "vendor", 3_GiB)}},
|
true))
|
<< "Should not be able to grow over maximum size of group";
|
}
|
|
TEST_P(BootControlAndroidGroupTestP, GroupTooBig) {
|
EXPECT_FALSE(bootctl_.InitPartitionMetadata(
|
target(),
|
PartitionMetadata{.groups = {{.name = "android", .size = 3_GiB},
|
{.name = "oem", .size = 3_GiB}}},
|
true))
|
<< "Should not be able to grow over size of super / 2";
|
}
|
|
TEST_P(BootControlAndroidGroupTestP, AddPartitionToGroup) {
|
ExpectStoreMetadata(PartitionMetadata{
|
.groups = {
|
{.name = T("android"),
|
.size = 3_GiB,
|
.partitions = {{.name = T("system"), .size = 2_GiB},
|
{.name = T("product_services"), .size = 1_GiB}}}}});
|
ExpectUnmap({T("system"), T("vendor"), T("product_services")});
|
|
EXPECT_TRUE(bootctl_.InitPartitionMetadata(
|
target(),
|
PartitionMetadata{
|
.groups = {{.name = "android",
|
.size = 3_GiB,
|
.partitions = {{.name = "system", .size = 2_GiB},
|
{.name = "product_services",
|
.size = 1_GiB}}},
|
SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}},
|
true));
|
}
|
|
TEST_P(BootControlAndroidGroupTestP, RemovePartitionFromGroup) {
|
ExpectStoreMetadata(PartitionMetadata{
|
.groups = {{.name = T("android"), .size = 3_GiB, .partitions = {}}}});
|
ExpectUnmap({T("vendor")});
|
|
EXPECT_TRUE(bootctl_.InitPartitionMetadata(
|
target(),
|
PartitionMetadata{
|
.groups = {{.name = "android", .size = 3_GiB, .partitions = {}},
|
SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}},
|
true));
|
}
|
|
TEST_P(BootControlAndroidGroupTestP, AddGroup) {
|
ExpectStoreMetadata(PartitionMetadata{
|
.groups = {
|
SimpleGroup(T("new_group"), 2_GiB, T("new_partition"), 2_GiB)}});
|
ExpectUnmap({T("system"), T("vendor"), T("new_partition")});
|
|
EXPECT_TRUE(bootctl_.InitPartitionMetadata(
|
target(),
|
PartitionMetadata{
|
.groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB),
|
SimpleGroup("oem", 1_GiB, "vendor", 1_GiB),
|
SimpleGroup("new_group", 2_GiB, "new_partition", 2_GiB)}},
|
true));
|
}
|
|
TEST_P(BootControlAndroidGroupTestP, RemoveGroup) {
|
ExpectStoreMetadataMatch(Not(HasGroup(T("oem"))));
|
ExpectUnmap({T("system")});
|
EXPECT_TRUE(bootctl_.InitPartitionMetadata(
|
target(),
|
PartitionMetadata{
|
.groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB)}},
|
true));
|
}
|
|
TEST_P(BootControlAndroidGroupTestP, ResizeGroup) {
|
ExpectStoreMetadata(PartitionMetadata{
|
.groups = {SimpleGroup(T("android"), 2_GiB, T("system"), 2_GiB),
|
SimpleGroup(T("oem"), 3_GiB, T("vendor"), 3_GiB)}});
|
ExpectUnmap({T("system"), T("vendor")});
|
|
EXPECT_TRUE(bootctl_.InitPartitionMetadata(
|
target(),
|
PartitionMetadata{
|
.groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB),
|
SimpleGroup("oem", 3_GiB, "vendor", 3_GiB)}},
|
true));
|
}
|
|
INSTANTIATE_TEST_CASE_P(BootControlAndroidTest,
|
BootControlAndroidGroupTestP,
|
testing::Values(TestParam{0, 1}, TestParam{1, 0}));
|
|
} // namespace chromeos_update_engine
|