| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "growable_slab.h" |
| |
| #include <ostream> |
| #include <unordered_map> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/testing/predicates/status.h" |
| |
| namespace vmo_store { |
| namespace testing { |
| |
| class SimpleType { |
| public: |
| SimpleType(uint32_t v) : value_(v) {} |
| |
| bool operator==(const SimpleType& rhs) const { return value_ == rhs.value_; } |
| |
| bool operator!=(const SimpleType& rhs) const { return !(rhs == *this); } |
| |
| friend std::ostream& operator<<(std::ostream& os, const SimpleType& type) { |
| os << "SimpleType(" << type.value_ << ")"; |
| return os; |
| } |
| |
| uint32_t get() const { return value_; } |
| |
| private: |
| uint32_t value_; |
| }; |
| |
| class MoveOnlyType { |
| public: |
| MoveOnlyType(uint32_t v) : value_(v) {} |
| MoveOnlyType(MoveOnlyType&& other) { |
| value_ = other.value_; |
| other.value_ = UINT32_MAX; |
| }; |
| |
| MoveOnlyType(const MoveOnlyType&) = delete; |
| MoveOnlyType& operator=(const MoveOnlyType& other) = delete; |
| |
| bool operator==(const MoveOnlyType& rhs) const { return value_ == rhs.value_; } |
| |
| bool operator!=(const MoveOnlyType& rhs) const { return !(rhs == *this); } |
| |
| friend std::ostream& operator<<(std::ostream& os, const MoveOnlyType& type) { |
| os << "MoveOnlyType(" << type.value_ << ")"; |
| return os; |
| } |
| |
| uint32_t get() const { return value_; } |
| |
| private: |
| uint32_t value_; |
| }; |
| |
| template <typename T> |
| class GrowableSlabTest : public ::testing::Test {}; |
| struct SimpleSize { |
| using Key = size_t; |
| using Value = SimpleType; |
| }; |
| struct SimpleU32 { |
| using Key = uint32_t; |
| using Value = SimpleType; |
| }; |
| struct MoveSize { |
| using Key = size_t; |
| using Value = MoveOnlyType; |
| }; |
| struct MoveU32 { |
| using Key = uint32_t; |
| using Value = MoveOnlyType; |
| }; |
| |
| using TestTypes = ::testing::Types<SimpleSize, SimpleU32, MoveSize, MoveU32>; |
| TYPED_TEST_SUITE(GrowableSlabTest, TestTypes); |
| |
| TYPED_TEST(GrowableSlabTest, Capacity) { |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| ASSERT_EQ(slab.capacity(), 0u); |
| ASSERT_EQ(slab.count(), 0u); |
| ASSERT_OK(slab.GrowTo(50)); |
| ASSERT_EQ(slab.capacity(), 50u); |
| ASSERT_EQ(slab.free(), 50u); |
| ASSERT_EQ(slab.count(), 0u); |
| ASSERT_OK(slab.GrowTo(20)); |
| ASSERT_EQ(slab.capacity(), 50u); |
| } |
| |
| TYPED_TEST(GrowableSlabTest, PushGet) { |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| using Key = typename TypeParam::Key; |
| constexpr Key kCapacity = 3; |
| ASSERT_OK(slab.GrowTo(kCapacity)); |
| for (Key i = 0; i < kCapacity; i++) { |
| auto key = slab.Push(i + 10); |
| ASSERT_TRUE(key.has_value()); |
| ASSERT_EQ(slab.capacity(), kCapacity); |
| ASSERT_EQ(slab.count(), i + 1); |
| ASSERT_EQ(slab.free(), kCapacity - i - 1); |
| |
| auto* value = slab.Get(*key); |
| ASSERT_TRUE(value); |
| ASSERT_EQ(*value, i + 10); |
| } |
| } |
| |
| TYPED_TEST(GrowableSlabTest, PushNoSpace) { |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| using Key = typename TypeParam::Key; |
| constexpr Key kCapacity = 3; |
| ASSERT_OK(slab.GrowTo(kCapacity)); |
| for (Key i = 0; i < kCapacity; i++) { |
| auto key = slab.Push(i + 10); |
| ASSERT_TRUE(key.has_value()); |
| } |
| auto key = slab.Push(1000); |
| ASSERT_FALSE(key.has_value()) << "Key has unexpected value " << *key; |
| } |
| |
| TYPED_TEST(GrowableSlabTest, Free) { |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| using Key = typename TypeParam::Key; |
| constexpr Key kCapacity = 3; |
| ASSERT_OK(slab.GrowTo(kCapacity)); |
| std::vector<std::tuple<Key, uint32_t>> keys; |
| for (Key i = 0; i < kCapacity; i++) { |
| uint32_t value = i + 10; |
| auto key = slab.Push(value); |
| ASSERT_TRUE(key.has_value()); |
| keys.emplace_back(*key, value); |
| } |
| Key expect_free = 0; |
| ASSERT_EQ(slab.free(), 0u); |
| for (const auto& [k, v] : keys) { |
| auto removed = slab.Erase(k); |
| expect_free++; |
| ASSERT_TRUE(removed.has_value()); |
| ASSERT_EQ(*removed, v); |
| ASSERT_EQ(slab.free(), expect_free); |
| ASSERT_EQ(slab.count(), kCapacity - expect_free); |
| } |
| // Check bad frees (including a key equal to capacity and one over it). |
| keys.emplace_back(kCapacity, 0); |
| keys.emplace_back(kCapacity + 10, 0); |
| for (auto& [k, _v] : keys) { |
| auto removed = slab.Erase(k); |
| ASSERT_FALSE(removed.has_value()) << "Unexpected remove value " << *removed << " on key " << k; |
| ASSERT_EQ(slab.free(), kCapacity); |
| } |
| } |
| |
| TYPED_TEST(GrowableSlabTest, PushFreeGet) { |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| using Key = typename TypeParam::Key; |
| constexpr Key kCapacity = 15; |
| ASSERT_OK(slab.GrowTo(kCapacity)); |
| std::vector<std::tuple<Key, uint32_t>> keys; |
| for (Key i = 0; i < kCapacity; i++) { |
| uint32_t value = i + 10; |
| auto key = slab.Push(value); |
| ASSERT_TRUE(key.has_value()); |
| keys.emplace_back(*key, value); |
| } |
| ASSERT_EQ(slab.count(), kCapacity); |
| ASSERT_EQ(slab.free(), 0u); |
| // Remove all odd values. |
| Key remove_count = 0; |
| for (const auto& [k, v] : keys) { |
| if (v & 1u) { |
| auto removed = slab.Erase(k); |
| ASSERT_TRUE(removed.has_value()); |
| ASSERT_EQ(*removed, v); |
| remove_count++; |
| } |
| } |
| ASSERT_EQ(slab.count(), kCapacity - remove_count); |
| ASSERT_EQ(slab.free(), remove_count); |
| // Check that we can get only the keys that still exist. |
| for (auto& [k, v] : keys) { |
| auto* value = slab.Get(k); |
| if (v & 1u) { |
| // Odd value was removed. |
| ASSERT_TRUE(value == nullptr) << "Unexpected valid value: " << *value << " for key " << k; |
| } else { |
| ASSERT_TRUE(value != nullptr); |
| ASSERT_EQ(*value, v); |
| } |
| } |
| // Reinsert the removed keys. |
| for (auto& [k, v] : keys) { |
| if (v & 1u) { |
| auto key = slab.Push(v); |
| ASSERT_TRUE(key.has_value()); |
| k = *key; |
| } |
| } |
| ASSERT_EQ(slab.count(), kCapacity); |
| ASSERT_EQ(slab.free(), 0u); |
| // Get all values again and check everything is in order. |
| for (const auto& [k, v] : keys) { |
| auto* value = slab.Get(k); |
| ASSERT_TRUE(value != nullptr); |
| ASSERT_EQ(*value, v); |
| } |
| } |
| |
| TYPED_TEST(GrowableSlabTest, Insert) { |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| using Key = typename TypeParam::Key; |
| constexpr Key kCapacity = 7; |
| constexpr Key kReservedKey = 4; |
| ASSERT_OK(slab.GrowTo(kCapacity)); |
| // All the keys up to capacity should be available, we'll use all of them but one and push at the |
| // end. |
| for (Key i = 0; i < kCapacity; i++) { |
| if (i != kReservedKey) { |
| ASSERT_OK(slab.Insert(i, i + 10)); |
| } |
| } |
| ASSERT_EQ(slab.count(), kCapacity - 1); |
| ASSERT_EQ(slab.free(), 1u); |
| auto key = slab.Push(999); |
| ASSERT_TRUE(key.has_value()); |
| ASSERT_EQ(*key, kReservedKey); |
| ASSERT_EQ(slab.free(), 0u); |
| ASSERT_EQ(slab.count(), kCapacity); |
| // Inserting a key equal to or greater than capacity is invalid. |
| ASSERT_EQ(slab.Insert(kCapacity, 1), ZX_ERR_OUT_OF_RANGE); |
| ASSERT_EQ(slab.Insert(kCapacity + 10, 1), ZX_ERR_OUT_OF_RANGE); |
| // Inserting a key that is already occupied is also invalid. |
| ASSERT_EQ(slab.Insert(0, 1), ZX_ERR_ALREADY_EXISTS); |
| ASSERT_TRUE(slab.Erase(0).has_value()); |
| ASSERT_OK(slab.Insert(0, 1)); |
| } |
| |
| TYPED_TEST(GrowableSlabTest, Grow) { |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| ASSERT_EQ(slab.capacity(), 0u); |
| ASSERT_OK(slab.Grow()); |
| ASSERT_EQ(slab.capacity(), 1u); |
| ASSERT_OK(slab.Grow()); |
| // Doesn't grow if we still have free slots. |
| ASSERT_EQ(slab.capacity(), 1u); |
| ASSERT_TRUE(slab.Push(1).has_value()); |
| ASSERT_OK(slab.Grow()); |
| ASSERT_EQ(slab.capacity(), 2u); |
| while (slab.free() != 0) { |
| ASSERT_TRUE(slab.Push(1).has_value()); |
| } |
| ASSERT_OK(slab.Grow()); |
| ASSERT_EQ(slab.capacity(), 4u); |
| } |
| |
| TYPED_TEST(GrowableSlabTest, Iterator) { |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| using Key = typename TypeParam::Key; |
| constexpr Key kCapacity = 15; |
| ASSERT_OK(slab.GrowTo(kCapacity)); |
| std::vector<std::pair<Key, uint32_t>> inserted; |
| ASSERT_EQ(slab.begin(), slab.end()); |
| for (Key i = 0; i < kCapacity; i++) { |
| auto value = static_cast<uint32_t>(i + 10); |
| auto key = slab.Push(value); |
| ASSERT_TRUE(key.has_value()); |
| inserted.emplace_back(*key, value); |
| } |
| ASSERT_NE(slab.begin(), slab.end()); |
| |
| // Iterate over the slab and the vector to match the ordering of the values. |
| auto it = inserted.begin(); |
| for (const auto& i : slab) { |
| ASSERT_NE(it, inserted.end()); |
| ASSERT_EQ(i.get(), it->second); |
| it++; |
| } |
| ASSERT_EQ(it, inserted.end()); |
| |
| // Remove all keys multiple of 3 from both the slab and the vector. |
| Key removed_count = 0; |
| for (auto i = inserted.begin(); i != inserted.end();) { |
| auto& [k, v] = *i; |
| if (v % 3 == 0) { |
| auto removed = slab.Erase(k); |
| ASSERT_TRUE(removed.has_value()); |
| ASSERT_EQ(*removed, v); |
| removed_count++; |
| i = inserted.erase(i); |
| } else { |
| i++; |
| } |
| } |
| |
| // Iterate again and check that the iterator is still sane. |
| it = inserted.begin(); |
| for (const auto& i : slab) { |
| ASSERT_NE(it, inserted.end()); |
| ASSERT_EQ(i.get(), it->second); |
| it++; |
| } |
| ASSERT_EQ(it, inserted.end()); |
| } |
| |
| TYPED_TEST(GrowableSlabTest, NoFastReuse) { |
| // Tests that the free list in the slab is a queue and not a stack, delaying reuse of old keys. |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| ASSERT_OK(slab.GrowTo(3)); |
| auto key1 = slab.Push(1); |
| ASSERT_TRUE(key1.has_value()); |
| auto key2 = slab.Push(2); |
| ASSERT_TRUE(key2.has_value()); |
| ASSERT_NE(*key1, *key2); |
| // Free key 1, and push a new value, assert that key1 is not immediately reused. |
| ASSERT_TRUE(slab.Erase(*key1).has_value()); |
| auto key3 = slab.Push(3); |
| ASSERT_TRUE(key3.has_value()); |
| ASSERT_NE(*key3, *key1); |
| ASSERT_NE(*key3, *key2); |
| } |
| |
| TYPED_TEST(GrowableSlabTest, Clear) { |
| GrowableSlab<typename TypeParam::Value, typename TypeParam::Key> slab; |
| using Key = typename TypeParam::Key; |
| constexpr Key kCapacity = 15; |
| ASSERT_OK(slab.GrowTo(kCapacity)); |
| while (slab.free() != 0) { |
| ASSERT_TRUE(slab.Push(1).has_value()); |
| } |
| ASSERT_EQ(slab.count(), kCapacity); |
| slab.Clear(); |
| ASSERT_EQ(slab.count(), 0u); |
| ASSERT_EQ(slab.begin(), slab.end()); |
| ASSERT_EQ(slab.free(), kCapacity); |
| } |
| |
| TEST(MiscTest, DestructorIsCalled) { |
| // A class that increments a counter on construction and decrements on destruction. We'll use it |
| // to make sure the destructors get called as expected. |
| class Value { |
| public: |
| explicit Value(size_t* counter) : counter_(counter) { *counter_ += 1; } |
| ~Value() { |
| if (counter_) { |
| *counter_ -= 1; |
| } |
| } |
| Value(Value&& other) noexcept { |
| counter_ = other.counter_; |
| other.counter_ = nullptr; |
| } |
| |
| private: |
| size_t* counter_; |
| }; |
| GrowableSlab<Value> slab; |
| constexpr size_t kCapacity = 6; |
| size_t counter = 0; |
| ASSERT_OK(slab.GrowTo(kCapacity)); |
| std::vector<size_t> keys; |
| while (slab.free() != 0) { |
| auto key = slab.Push(Value(&counter)); |
| ASSERT_TRUE(key.has_value()); |
| keys.push_back(*key); |
| } |
| ASSERT_EQ(counter, kCapacity); |
| for (size_t i = 0; i < kCapacity / 2; i++) { |
| slab.Erase(keys[i]); |
| ASSERT_EQ(counter, kCapacity - i - 1u); |
| } |
| slab.Clear(); |
| ASSERT_EQ(counter, 0u); |
| } |
| |
| } // namespace testing |
| } // namespace vmo_store |