| // 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 <zircon/status.h> |
| |
| #include <unordered_map> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/testing/predicates/status.h" |
| #include "vmo_store.h" |
| |
| namespace vmo_store { |
| namespace testing { |
| |
| #define ASSERT_RESULT(result) \ |
| ASSERT_TRUE(result.is_ok()) << "Unexpected error result: " << zx_status_get_string(result.error()) |
| |
| namespace { |
| |
| // Move-only metadata class proving that StoredVmo can store move-only values. |
| class MoveOnlyMeta { |
| public: |
| MoveOnlyMeta(uint64_t value) : meta_(value) {} |
| MoveOnlyMeta(MoveOnlyMeta&& other) = default; |
| MoveOnlyMeta& operator=(MoveOnlyMeta&&) = default; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(MoveOnlyMeta); |
| |
| uint64_t meta() const { return meta_; } |
| |
| private: |
| uint64_t meta_; |
| }; |
| |
| zx::vmo MakeVmo() { |
| zx::vmo vmo; |
| EXPECT_EQ(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo), ZX_OK); |
| return vmo; |
| } |
| |
| template <typename M> |
| StoredVmo<M> MakeStoredVmoHelper(zx::vmo vmo, uint64_t v) { |
| return StoredVmo<M>(std::move(vmo), static_cast<M>(v)); |
| } |
| |
| template <> |
| StoredVmo<MoveOnlyMeta> MakeStoredVmoHelper(zx::vmo vmo, uint64_t v) { |
| return StoredVmo<MoveOnlyMeta>(std::move(vmo), MoveOnlyMeta(v)); |
| } |
| |
| template <> |
| StoredVmo<void> MakeStoredVmoHelper(zx::vmo vmo, uint64_t) { |
| return StoredVmo<void>(std::move(vmo)); |
| } |
| |
| template <typename M> |
| void CompareMeta(StoredVmo<M>* vmo, uint64_t compare) { |
| ASSERT_EQ(vmo->meta(), static_cast<M>(compare)); |
| } |
| |
| template <> |
| void CompareMeta(StoredVmo<MoveOnlyMeta>* vmo, uint64_t compare) { |
| ASSERT_EQ(vmo->meta().meta(), compare); |
| } |
| |
| template <> |
| void CompareMeta(StoredVmo<void>* vmo, uint64_t compare) {} |
| |
| template <typename K> |
| K MakeKey(uint64_t key) { |
| return key; |
| } |
| |
| template <> |
| std::string MakeKey(uint64_t key) { |
| std::stringstream ss; |
| ss << key; |
| return ss.str(); |
| } |
| |
| } // namespace |
| |
| // An implementation of AbstractStorage to test the dynamic dispatch backing store. |
| // Also proves that keys may be non-integral values. |
| class UnorderedMapStorage : public AbstractStorage<std::string, int32_t> { |
| public: |
| zx_status_t Reserve(size_t capacity) override { return ZX_OK; } |
| |
| zx_status_t Insert(std::string key, StoredVmo<int32_t>&& vmo) override { |
| if (map_.find(key) != map_.end()) { |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| map_.emplace(key, std::move(vmo)); |
| return ZX_OK; |
| } |
| |
| fit::optional<std::string> Push(StoredVmo<int32_t>&& vmo) override { |
| while (map_.find(auto_keys_) != map_.end()) { |
| auto_keys_ += "a"; |
| } |
| map_.emplace(auto_keys_, std::move(vmo)); |
| return auto_keys_; |
| } |
| |
| StoredVmo<int32_t>* Get(const std::string& key) override { |
| auto srch = map_.find(key); |
| if (srch == map_.end()) { |
| return nullptr; |
| } |
| return &srch->second; |
| } |
| |
| bool Erase(std::string key) override { return map_.erase(key) != 0; } |
| |
| size_t count() const override { return map_.size(); } |
| |
| private: |
| std::unordered_map<std::string, StoredVmo<int32_t>> map_; |
| std::string auto_keys_ = "a"; |
| }; |
| |
| class TestDynamicStorage : public DynamicDispatchStorage<std::string, int32_t> { |
| public: |
| TestDynamicStorage() |
| : DynamicDispatchStorage(DynamicDispatchStorage::BasePtr(new UnorderedMapStorage)) {} |
| }; |
| |
| template <typename T> |
| class TypedStorageTest : public ::testing::Test { |
| public: |
| using VmoStore = ::vmo_store::VmoStore<T>; |
| static constexpr size_t kStorageCapacity = 16; |
| |
| TypedStorageTest() : store_(Options()) {} |
| |
| void SetUp() override { ASSERT_OK(store_.Reserve(kStorageCapacity)); } |
| |
| static typename VmoStore::StoredVmo MakeStoredVmo(uint64_t meta = 0) { |
| return MakeStoredVmoHelper<typename VmoStore::Meta>(MakeVmo(), meta); |
| } |
| |
| VmoStore store_; |
| }; |
| |
| using TestTypes = ::testing::Types<SlabStorage<uint64_t, void>, SlabStorage<uint64_t, int32_t>, |
| SlabStorage<uint8_t>, HashTableStorage<uint64_t, void>, |
| HashTableStorage<uint64_t, int32_t>, HashTableStorage<uint8_t>, |
| SlabStorage<uint64_t, MoveOnlyMeta>, TestDynamicStorage>; |
| |
| TYPED_TEST_SUITE(TypedStorageTest, TestTypes); |
| |
| TYPED_TEST(TypedStorageTest, BasicStoreOperations) { |
| auto& store = this->store_; |
| auto vmo = TestFixture::MakeStoredVmo(1); |
| auto vmo1 = vmo.vmo(); |
| auto result = store.Register(std::move(vmo)); |
| ASSERT_RESULT(result); |
| auto k1 = result.take_value(); |
| vmo = TestFixture::MakeStoredVmo(2); |
| auto vmo2 = vmo.vmo(); |
| result = store.Register(std::move(vmo)); |
| ASSERT_RESULT(result); |
| auto k2 = result.take_value(); |
| ASSERT_NE(k1, k2); |
| auto k3 = MakeKey<typename TestFixture::VmoStore::Key>(TestFixture::kStorageCapacity / 2); |
| vmo = TestFixture::MakeStoredVmo(3); |
| auto vmo3 = vmo.vmo(); |
| ASSERT_OK(store.RegisterWithKey(k3, std::move(vmo))) << "Failed to register with key " << k3; |
| |
| // Can't insert with a used key. |
| ASSERT_STATUS(store.RegisterWithKey(k1, TestFixture::MakeStoredVmo()), ZX_ERR_ALREADY_EXISTS); |
| |
| auto* retrieved = store.GetVmo(k1); |
| ASSERT_TRUE(retrieved); |
| ASSERT_EQ(retrieved->vmo()->get(), vmo1->get()); |
| ASSERT_NO_FATAL_FAILURE(CompareMeta(retrieved, 1)); |
| |
| retrieved = store.GetVmo(k2); |
| ASSERT_TRUE(retrieved); |
| ASSERT_EQ(retrieved->vmo()->get(), vmo2->get()); |
| ASSERT_NO_FATAL_FAILURE(CompareMeta(retrieved, 2)); |
| |
| retrieved = store.GetVmo(k3); |
| ASSERT_TRUE(retrieved); |
| ASSERT_EQ(retrieved->vmo()->get(), vmo3->get()); |
| ASSERT_NO_FATAL_FAILURE(CompareMeta(retrieved, 3)); |
| |
| ASSERT_EQ(store.count(), 3u); |
| |
| // Unregister k3 and check that we can't get it anymore nor erase it again. |
| ASSERT_OK(store.Unregister(k3)); |
| ASSERT_STATUS(store.Unregister(k3), ZX_ERR_NOT_FOUND); |
| ASSERT_EQ(store.GetVmo(k3), nullptr); |
| // Check that the VMO handle got destroyed. |
| uint64_t vmo_size; |
| ASSERT_STATUS(vmo3->get_size(&vmo_size), ZX_ERR_BAD_HANDLE); |
| |
| ASSERT_EQ(store.count(), 2u); |
| |
| // Attempting to register a VMO with an invalid handle will cause an error. |
| auto error_result = |
| store.Register(MakeStoredVmoHelper<typename TestFixture::VmoStore::Meta>(zx::vmo(), 0)); |
| ASSERT_TRUE(error_result.is_error()) << "Unexpected key result " << error_result.value(); |
| ASSERT_STATUS(error_result.error(), ZX_ERR_BAD_HANDLE); |
| ASSERT_STATUS(store.RegisterWithKey( |
| k1, MakeStoredVmoHelper<typename TestFixture::VmoStore::Meta>(zx::vmo(), 0)), |
| ZX_ERR_BAD_HANDLE); |
| } |
| |
| } // namespace testing |
| } // namespace vmo_store |