// // 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 #include #include #include #include #include #include #include #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; // "{name_a, size}" using PartitionSuffixSizes = std::map; 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 std::ostream& operator<<(std::ostream& os, const std::map& param) { os << "{"; bool first = true; for (const auto& pair : param) { if (!first) os << ", "; os << pair.first << ":" << pair.second; first = false; } return os << "}"; } template std::ostream& operator<<(std::ostream& os, const std::vector& 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 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 { 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 MetadataMatches( const PartitionSuffixSizes& partition_sizes) { return MakeMatcher(new MetadataMatcher(partition_sizes)); } inline Matcher 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(); bootctl_.dynamic_control_ = std::make_unique>(); 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& module() { return static_cast&>(*bootctl_.module_); } // Return the mocked DynamicPartitionControlInterface. NiceMock& dynamicControl() { return static_cast&>( *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& 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& 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& 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 mapped_devices_; }; class BootControlAndroidTestP : public BootControlAndroidTest, public ::testing::WithParamInterface { 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& 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