From c236a25339bca51d393c77f7a6945d36c6234943 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 26 Apr 2021 12:11:23 +0200 Subject: [PATCH] QmlDesigner: Add storage cache Task-number: QDS-4237 Change-Id: I7d587f615b5e26cc6014ea3a63dedd203fdd35eb Reviewed-by: Thomas Hartmann Reviewed-by: Qt CI Bot --- .../designercore/metainfo/storagecache.h | 337 ++++++++++ .../metainfo/storagecachealgorithms.h | 72 ++ .../designercore/metainfo/storagecacheentry.h | 50 ++ .../designercore/metainfo/storagecachefwd.h | 44 ++ .../qmldesigner/qmldesignerunittestfiles.pri | 4 + tests/unit/unittest/storagecache-test.cpp | 625 ++++++++++++++++++ tests/unit/unittest/unittest.pro | 1 + 7 files changed, 1133 insertions(+) create mode 100644 src/plugins/qmldesigner/designercore/metainfo/storagecache.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/storagecachealgorithms.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/storagecacheentry.h create mode 100644 src/plugins/qmldesigner/designercore/metainfo/storagecachefwd.h create mode 100644 tests/unit/unittest/storagecache-test.cpp diff --git a/src/plugins/qmldesigner/designercore/metainfo/storagecache.h b/src/plugins/qmldesigner/designercore/metainfo/storagecache.h new file mode 100644 index 00000000000..e8a5ff19208 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/storagecache.h @@ -0,0 +1,337 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "storagecachealgorithms.h" +#include "storagecacheentry.h" +#include "storagecachefwd.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace QmlDesigner { + +class StorageCacheException : public std::exception +{ + const char *what() const noexcept override + { + return "StorageCache entries are in invalid state."; + } +}; + +class NonLockingMutex +{ +public: + constexpr NonLockingMutex() noexcept {} + NonLockingMutex(const NonLockingMutex&) = delete; + NonLockingMutex& operator=(const NonLockingMutex&) = delete; + void lock() {} + void unlock() {} + void lock_shared() {} + void unlock_shared() {} +}; + +template, + typename FetchValue = std::function, + typename FetchId = std::function> +class StorageCache +{ + template + friend class StorageCache; + + using ResultType = std::conditional_t::value, ViewType, Type>; + +public: + using MutexType = Mutex; + using CacheEntries = std::vector; + using const_iterator = typename CacheEntries::const_iterator; + using Found = QmlDesigner::Found; + + StorageCache(FetchValue fetchValue, FetchId fetchId, std::size_t reserveSize = 1024) + : m_fetchValue{std::move(fetchValue)} + , m_fetchId{std::move(fetchId)} + { + m_entries.reserve(reserveSize); + m_indices.reserve(reserveSize); + } + + StorageCache(const StorageCache &other) + : m_entries(other.m_entries) + , m_indices(other.m_indices) + {} + + template + Cache clone() + { + Cache cache; + cache.m_entries = m_entries; + cache.m_indices = m_indices; + + return cache; + } + + StorageCache(StorageCache &&other) + : m_entries(std::move(other.m_entries)) + , m_indices(std::move(other.m_indices)) + {} + + StorageCache &operator=(StorageCache &&other) + { + m_entries = std::move(other.m_entries); + m_indices = std::move(other.m_indices); + + return *this; + } + + void populate(CacheEntries &&entries) + { + uncheckedPopulate(std::move(entries)); + + checkEntries(); + } + + void uncheckedPopulate(CacheEntries &&entries) + { + std::sort(entries.begin(), entries.end(), [](ViewType first, ViewType second) { + return compare(first, second) < 0; + }); + + m_entries = std::move(entries); + + int max_id = 0; + + auto found = std::max_element(m_entries.begin(), + m_entries.end(), + [](const auto &first, const auto &second) { + return first.id < second.id; + }); + + if (found != m_entries.end()) + max_id = found->id + 1; + + m_indices.resize(max_id, -1); + + updateIndices(); + } + + void add(std::vector &&views) + { + auto less = [](ViewType first, ViewType second) { return compare(first, second) < 0; }; + + std::sort(views.begin(), views.end(), less); + + views.erase(std::unique(views.begin(), views.end()), views.end()); + + CacheEntries newCacheEntries; + newCacheEntries.reserve(views.size()); + + std::set_difference(views.begin(), + views.end(), + m_entries.begin(), + m_entries.end(), + Utils::make_iterator([&](ViewType newView) { + IndexType index = m_fetchId(newView); + newCacheEntries.emplace_back(newView, index); + }), + less); + + if (newCacheEntries.size()) { + auto found = std::max_element(newCacheEntries.begin(), + newCacheEntries.end(), + [](const auto &first, const auto &second) { + return first.id < second.id; + }); + + int max_id = found->id + 1; + + if (max_id > int(m_indices.size())) + m_indices.resize(max_id, -1); + + CacheEntries mergedCacheEntries; + mergedCacheEntries.reserve(newCacheEntries.size() + m_entries.size()); + + std::merge(std::make_move_iterator(m_entries.begin()), + std::make_move_iterator(m_entries.end()), + std::make_move_iterator(newCacheEntries.begin()), + std::make_move_iterator(newCacheEntries.end()), + std::back_inserter(mergedCacheEntries), + less); + + m_entries = std::move(mergedCacheEntries); + + updateIndices(); + } + } + + IndexType id(ViewType view) + { + std::shared_lock sharedLock(m_mutex); + + Found found = find(view); + + if (found.wasFound) + return found.iterator->id; + + sharedLock.unlock(); + std::lock_guard exclusiveLock(m_mutex); + + if (!std::is_base_of::value) + found = find(view); + if (!found.wasFound) { + IndexType index = insertEntry(found.iterator, view, m_fetchId(view)); + found.iterator = m_entries.begin() + index; + } + + return found.iterator->id; + } + + template + std::vector ids(const Container &values) + { + std::vector ids; + ids.reserve(values.size()); + + std::transform(values.begin(), values.end(), std::back_inserter(ids), [&](const auto &values) { + return this->id(values); + }); + + return ids; + } + + std::vector ids(std::initializer_list values) + { + return ids>(values); + } + + ResultType value(IndexType id) + { + std::shared_lock sharedLock(m_mutex); + + if (IndexType(m_indices.size()) > id && m_indices.at(id) >= 0) + return m_entries.at(m_indices.at(id)).value; + + sharedLock.unlock(); + std::lock_guard exclusiveLock(m_mutex); + IndexType index; + + Type value{m_fetchValue(id)}; + index = insertEntry(find(value).iterator, value, id); + + return std::move(m_entries[index].value); + } + + std::vector values(const std::vector &ids) const + { + std::shared_lock sharedLock(m_mutex); + + std::vector values; + values.reserve(ids.size()); + + for (IndexType id : ids) + values.emplace_back(m_entries.at(m_indices.at(id)).value); + + return values; + } + + bool isEmpty() const { return m_entries.empty() && m_indices.empty(); } + + Mutex &mutex() const + { + return m_mutex; + } + +private: + void updateIndices() + { + auto begin = m_entries.cbegin(); + for (auto current = begin; current != m_entries.cend(); ++current) + m_indices[current->id] = std::distance(begin, current); + } + + Found find(ViewType view) + { + return findInSorted(m_entries.cbegin(), m_entries.cend(), view, compare); + } + + void incrementLargerOrEqualIndicesByOne(IndexType newIndex) + { + std::transform(m_indices.begin(), + m_indices.end(), + m_indices.begin(), + [&] (IndexType index) { + return index >= newIndex ? ++index : index; + }); + } + + void ensureSize(IndexType id) + { + if (m_indices.size() <= std::size_t(id)) + m_indices.resize(id + 1, -1); + } + + IndexType insertEntry(const_iterator beforeIterator, ViewType view, IndexType id) + { + auto inserted = m_entries.emplace(beforeIterator, view, id); + + auto newIndex = IndexType(std::distance(m_entries.begin(), inserted)); + + incrementLargerOrEqualIndicesByOne(newIndex); + + ensureSize(id); + m_indices.at(id) = newIndex; + + return newIndex; + } + + void checkEntries() + { + for (const auto &entry : m_entries) { + if (entry.value != value(entry.id) || entry.id != id(entry.value)) + throw StorageCacheException(); + } + } + +private: + CacheEntries m_entries; + std::vector m_indices; + mutable Mutex m_mutex; + FetchValue m_fetchValue; + FetchId m_fetchId; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/storagecachealgorithms.h b/src/plugins/qmldesigner/designercore/metainfo/storagecachealgorithms.h new file mode 100644 index 00000000000..42303ca3891 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/storagecachealgorithms.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include + +#include + +namespace QmlDesigner { + +template +class Found +{ +public: + Iterator iterator; + bool wasFound; +}; + +template +Found findInSorted(ForwardIterator first, ForwardIterator last, const Type& value, Compare compare) +{ + ForwardIterator current; + using DifferenceType = typename std::iterator_traits::difference_type; + DifferenceType count{std::distance(first, last)}; + DifferenceType step; + + while (count > 0) { + current = first; + step = count / 2; + std::advance(current, step); + auto comparison = compare(*current, value); + if (comparison < 0) { + first = ++current; + count -= step + 1; + } else if (comparison > 0) { + count = step; + } else { + return {current, true}; + } + } + + return {first, false}; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/storagecacheentry.h b/src/plugins/qmldesigner/designercore/metainfo/storagecacheentry.h new file mode 100644 index 00000000000..f121c6df2fa --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/storagecacheentry.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +namespace QmlDesigner { + +template +class StorageCacheEntry +{ +public: + StorageCacheEntry(ViewType value, IndexType id) + : value(value) + , id(id) + {} + + operator ViewType() const { return value; } + friend bool operator==(const StorageCacheEntry &first, const StorageCacheEntry &second) + { + return first.id == second.id && first.value == second.value; + } + +public: + Type value; + IndexType id; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/metainfo/storagecachefwd.h b/src/plugins/qmldesigner/designercore/metainfo/storagecachefwd.h new file mode 100644 index 00000000000..8a8b030d96a --- /dev/null +++ b/src/plugins/qmldesigner/designercore/metainfo/storagecachefwd.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace QmlDesigner { + +class NonLockingMutex; + +template +class StorageCache; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri index a50be7f644f..161006d83c4 100644 --- a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri +++ b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri @@ -53,6 +53,10 @@ HEADERS += \ $$PWD/designercore/include/import.h \ $$PWD/designercore/include/abstractproperty.h \ $$PWD/designercore/include/abstractview.h \ + $$PWD/designercore/metainfo/storagecache.h \ + $$PWD/designercore/metainfo/storagecachealgorithms.h \ + $$PWD/designercore/metainfo/storagecacheentry.h \ + $$PWD/designercore/metainfo/storagecachefwd.h \ $$PWD/designercore/model/model_p.h \ $$PWD/designercore/include/qmldesignercorelib_global.h \ $$PWD/designercore/model/internalbindingproperty.h \ diff --git a/tests/unit/unittest/storagecache-test.cpp b/tests/unit/unittest/storagecache-test.cpp new file mode 100644 index 00000000000..4b673fe44c4 --- /dev/null +++ b/tests/unit/unittest/storagecache-test.cpp @@ -0,0 +1,625 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "googletest.h" + +#include "mockfilepathstorage.h" +#include "mockmutex.h" +#include "sqlitedatabasemock.h" + +#include + +#include + +namespace { + +using QmlDesigner::StorageCacheException; + +using uint64 = unsigned long long; + +using QmlDesigner::findInSorted; +using Utils::compare; +using Utils::reverseCompare; +using StorageIdFunction = std::function; +using StorageStringFunction = std::function; + +using CacheWithMockLocking = QmlDesigner::StorageCache, + decltype(&Utils::reverseCompare), + Utils::reverseCompare>; + +using CacheWithoutLocking = QmlDesigner::StorageCache, + decltype(&Utils::reverseCompare), + Utils::reverseCompare>; + +template +class StorageCache : public testing::Test +{ +protected: + void SetUp() + { + std::sort(filePaths.begin(), filePaths.end(), [](auto &f, auto &l) { + return compare(f, l) < 0; + }); + std::sort(reverseFilePaths.begin(), reverseFilePaths.end(), [](auto &f, auto &l) { + return reverseCompare(f, l) < 0; + }); + + ON_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))).WillByDefault(Return(42)); + ON_CALL(this->mockStorage, fetchDirectoryId(Eq("bar"))).WillByDefault(Return(43)); + ON_CALL(this->mockStorage, fetchDirectoryId(Eq("poo"))).WillByDefault(Return(44)); + ON_CALL(this->mockStorage, fetchDirectoryId(Eq("taa"))).WillByDefault(Return(45)); + ON_CALL(this->mockStorage, fetchDirectoryPath(41)).WillByDefault(Return(Utils::PathString("bar"))); + ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath1))).WillByDefault(Return(0)); + ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath2))).WillByDefault(Return(1)); + ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath3))).WillByDefault(Return(2)); + ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath4))).WillByDefault(Return(3)); + ON_CALL(this->mockStorage, fetchDirectoryId(Eq(filePath5))).WillByDefault(Return(4)); + } + +protected: + NiceMock databaseMock; + NiceMock mockStorage{databaseMock}; + StorageIdFunction mockStorageFetchDirectyId{ + [&](Utils::SmallStringView string) { return mockStorage.fetchDirectoryId(string); }}; + StorageStringFunction mockStorageFetchDirectyPath{ + [&](int id) { return mockStorage.fetchDirectoryPath(id); }}; + Cache cache{mockStorageFetchDirectyPath, mockStorageFetchDirectyId}; + typename Cache::MutexType &mockMutex = cache.mutex(); + Utils::PathString filePath1{"/file/pathOne"}; + Utils::PathString filePath2{"/file/pathTwo"}; + Utils::PathString filePath3{"/file/pathThree"}; + Utils::PathString filePath4{"/file/pathFour"}; + Utils::PathString filePath5{"/file/pathFife"}; + Utils::PathStringVector filePaths{filePath1, + filePath2, + filePath3, + filePath4, + filePath5}; + Utils::PathStringVector reverseFilePaths{filePath1, + filePath2, + filePath3, + filePath4, + filePath5}; +}; + +using CacheTypes = ::testing::Types; +TYPED_TEST_SUITE(StorageCache, CacheTypes); + +TYPED_TEST(StorageCache, AddFilePath) +{ + auto id = this->cache.id(this->filePath1); + + ASSERT_THAT(id, 0); +} + +TYPED_TEST(StorageCache, AddSecondFilePath) +{ + this->cache.id(this->filePath1); + + auto id = this->cache.id(this->filePath2); + + ASSERT_THAT(id, 1); +} + +TYPED_TEST(StorageCache, AddDuplicateFilePath) +{ + this->cache.id(this->filePath1); + + auto id = this->cache.id(this->filePath1); + + ASSERT_THAT(id, 0); +} + +TYPED_TEST(StorageCache, AddDuplicateFilePathBetweenOtherEntries) +{ + this->cache.id(this->filePath1); + this->cache.id(this->filePath2); + this->cache.id(this->filePath3); + this->cache.id(this->filePath4); + + auto id = this->cache.id(this->filePath3); + + ASSERT_THAT(id, 2); +} + +TYPED_TEST(StorageCache, GetFilePathForIdWithOneEntry) +{ + this->cache.id(this->filePath1); + + auto filePath = this->cache.value(0); + + ASSERT_THAT(filePath, this->filePath1); +} + +TYPED_TEST(StorageCache, GetFilePathForIdWithSomeEntries) +{ + this->cache.id(this->filePath1); + this->cache.id(this->filePath2); + this->cache.id(this->filePath3); + this->cache.id(this->filePath4); + + auto filePath = this->cache.value(2); + + ASSERT_THAT(filePath, this->filePath3); +} + +TYPED_TEST(StorageCache, GetAllFilePaths) +{ + this->cache.id(this->filePath1); + this->cache.id(this->filePath2); + this->cache.id(this->filePath3); + this->cache.id(this->filePath4); + + auto filePaths = this->cache.values({0, 1, 2, 3}); + + ASSERT_THAT(filePaths, + ElementsAre(this->filePath1, this->filePath2, this->filePath3, this->filePath4)); +} + +TYPED_TEST(StorageCache, AddFilePaths) +{ + auto ids = this->cache.ids({this->filePath1, this->filePath2, this->filePath3, this->filePath4}); + + ASSERT_THAT(ids, ElementsAre(0, 1, 2, 3)); +} + +TYPED_TEST(StorageCache, AddFilePathsWithStorageFunction) +{ + auto ids = this->cache.ids({"foo", "taa", "poo", "bar"}); + + ASSERT_THAT(ids, UnorderedElementsAre(42, 43, 44, 45)); +} + +TYPED_TEST(StorageCache, IsEmpty) +{ + auto isEmpty = this->cache.isEmpty(); + + ASSERT_TRUE(isEmpty); +} + +TYPED_TEST(StorageCache, IsNotEmpty) +{ + this->cache.id(this->filePath1); + + auto isEmpty = this->cache.isEmpty(); + + ASSERT_FALSE(isEmpty); +} + +TYPED_TEST(StorageCache, PopulateWithEmptyVector) +{ + typename TypeParam::CacheEntries entries; + + this->cache.uncheckedPopulate(std::move(entries)); + + ASSERT_TRUE(this->cache.isEmpty()); +} + +TYPED_TEST(StorageCache, IsNotEmptyAfterPopulateWithSomeEntries) +{ + typename TypeParam::CacheEntries entries{{this->filePath1.clone(), 0}, + {this->filePath2.clone(), 3}, + {this->filePath3.clone(), 2}, + {this->filePath4.clone(), 5}}; + + this->cache.uncheckedPopulate(std::move(entries)); + + ASSERT_TRUE(!this->cache.isEmpty()); +} + +TYPED_TEST(StorageCache, GetEntryAfterPopulateWithSomeEntries) +{ + typename TypeParam::CacheEntries entries{{this->filePath1.clone(), 0}, + {this->filePath2.clone(), 1}, + {this->filePath3.clone(), 7}, + {this->filePath4.clone(), 3}}; + this->cache.uncheckedPopulate(std::move(entries)); + + auto value = this->cache.value(7); + + ASSERT_THAT(value, this->filePath3); +} + +TYPED_TEST(StorageCache, EntriesHaveUniqueIds) +{ + typename TypeParam::CacheEntries entries{{this->filePath1.clone(), 0}, + {this->filePath2.clone(), 1}, + {this->filePath3.clone(), 2}, + {this->filePath4.clone(), 2}}; + + ASSERT_THROW(this->cache.populate(std::move(entries)), StorageCacheException); +} + +TYPED_TEST(StorageCache, MultipleEntries) +{ + typename TypeParam::CacheEntries entries{{this->filePath1.clone(), 0}, + {this->filePath1.clone(), 1}, + {this->filePath3.clone(), 2}, + {this->filePath4.clone(), 3}}; + + ASSERT_THROW(this->cache.populate(std::move(entries)), StorageCacheException); +} + +TYPED_TEST(StorageCache, DontFindInSorted) +{ + auto found = findInSorted(this->filePaths.cbegin(), this->filePaths.cend(), "/file/pathFoo", compare); + + ASSERT_FALSE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedOne) +{ + auto found = findInSorted(this->filePaths.cbegin(), this->filePaths.cend(), "/file/pathOne", compare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedTwo) +{ + auto found = findInSorted(this->filePaths.cbegin(), this->filePaths.cend(), "/file/pathTwo", compare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedTree) +{ + auto found = findInSorted(this->filePaths.cbegin(), + this->filePaths.cend(), + "/file/pathThree", + compare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedFour) +{ + auto found = findInSorted(this->filePaths.cbegin(), this->filePaths.cend(), "/file/pathFour", compare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedFife) +{ + auto found = findInSorted(this->filePaths.cbegin(), this->filePaths.cend(), "/file/pathFife", compare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, DontFindInSortedReverse) +{ + auto found = findInSorted(this->reverseFilePaths.cbegin(), + this->reverseFilePaths.cend(), + "/file/pathFoo", + reverseCompare); + + ASSERT_FALSE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedOneReverse) +{ + auto found = findInSorted(this->reverseFilePaths.cbegin(), + this->reverseFilePaths.cend(), + "/file/pathOne", + reverseCompare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedTwoReverse) +{ + auto found = findInSorted(this->reverseFilePaths.cbegin(), + this->reverseFilePaths.cend(), + "/file/pathTwo", + reverseCompare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedTreeReverse) +{ + auto found = findInSorted(this->reverseFilePaths.cbegin(), + this->reverseFilePaths.cend(), + "/file/pathThree", + reverseCompare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedFourReverse) +{ + auto found = findInSorted(this->reverseFilePaths.cbegin(), + this->reverseFilePaths.cend(), + "/file/pathFour", + reverseCompare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, FindInSortedFifeReverse) +{ + auto found = findInSorted(this->reverseFilePaths.cbegin(), + this->reverseFilePaths.cend(), + "/file/pathFife", + reverseCompare); + + ASSERT_TRUE(found.wasFound); +} + +TYPED_TEST(StorageCache, IdIsReadAndWriteLockedForUnknownEntry) +{ + InSequence s; + + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + EXPECT_CALL(this->mockMutex, lock()); + EXPECT_CALL(this->mockMutex, unlock()); + + this->cache.id("foo"); +} + +TYPED_TEST(StorageCache, IdWithStorageFunctionIsReadAndWriteLockedForUnknownEntry) +{ + InSequence s; + + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + EXPECT_CALL(this->mockMutex, lock()); + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))); + EXPECT_CALL(this->mockMutex, unlock()); + + this->cache.id("foo"); +} + +TYPED_TEST(StorageCache, IdWithStorageFunctionIsReadLockedForKnownEntry) +{ + InSequence s; + this->cache.id("foo"); + + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + EXPECT_CALL(this->mockMutex, lock()).Times(0); + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))).Times(0); + EXPECT_CALL(this->mockMutex, unlock()).Times(0); + + this->cache.id("foo"); +} + +TYPED_TEST(StorageCache, IdIsReadLockedForKnownEntry) +{ + this->cache.id("foo"); + + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + EXPECT_CALL(this->mockMutex, lock()).Times(0); + EXPECT_CALL(this->mockMutex, unlock()).Times(0); + + this->cache.id("foo"); +} + +TYPED_TEST(StorageCache, IdsIsLocked) +{ + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + + this->cache.ids({"foo"}); +} + +TYPED_TEST(StorageCache, IdsWithStorageIsLocked) +{ + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + + this->cache.ids({"foo"}); +} + +TYPED_TEST(StorageCache, ValueIsLocked) +{ + auto id = this->cache.id("foo"); + + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + + this->cache.value(id); +} + +TYPED_TEST(StorageCache, ValuesIsLocked) +{ + auto ids = this->cache.ids({"foo", "bar"}); + + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + + this->cache.values(ids); +} + +TYPED_TEST(StorageCache, ValueWithStorageFunctionIsReadAndWriteLockedForUnknownId) +{ + InSequence s; + + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + EXPECT_CALL(this->mockMutex, lock()); + EXPECT_CALL(this->mockStorage, fetchDirectoryPath(Eq(41))); + EXPECT_CALL(this->mockMutex, unlock()); + + this->cache.value(41); +} + +TYPED_TEST(StorageCache, ValueWithStorageFunctionIsReadLockedForKnownId) +{ + InSequence s; + this->cache.value(41); + + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + EXPECT_CALL(this->mockMutex, lock()).Times(0); + EXPECT_CALL(this->mockStorage, fetchDirectoryPath(Eq(41))).Times(0); + EXPECT_CALL(this->mockMutex, unlock()).Times(0); + + this->cache.value(41); +} + +TYPED_TEST(StorageCache, IdWithStorageFunctionWhichHasNoEntryIsCallingStorageFunction) +{ + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))); + + this->cache.id("foo"); +} + +TYPED_TEST(StorageCache, IdWithStorageFunctionWhichHasEntryIsNotCallingStorageFunction) +{ + this->cache.id("foo"); + + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))).Times(0); + + this->cache.id("foo"); +} + +TYPED_TEST(StorageCache, IndexOfIdWithStorageFunctionWhichHasEntry) +{ + this->cache.id("foo"); + + auto index = this->cache.id("foo"); + + ASSERT_THAT(index, 42); +} + +TYPED_TEST(StorageCache, IndexOfIdWithStorageFunctionWhichHasNoEntry) +{ + auto index = this->cache.id("foo"); + + ASSERT_THAT(index, 42); +} + +TYPED_TEST(StorageCache, GetEntryByIndexAfterInsertingByCustomIndex) +{ + auto index = this->cache.id("foo"); + + auto value = this->cache.value(index); + + ASSERT_THAT(value, Eq("foo")); +} + +TYPED_TEST(StorageCache, CallFetchDirectoryPathForLowerIndex) +{ + auto index = this->cache.id("foo"); + + EXPECT_CALL(this->mockStorage, fetchDirectoryPath(Eq(index - 1))); + + this->cache.value(index - 1); +} + +TYPED_TEST(StorageCache, CallFetchDirectoryPathForUnknownIndex) +{ + EXPECT_CALL(this->mockStorage, fetchDirectoryPath(Eq(0))); + + this->cache.value(0); +} + +TYPED_TEST(StorageCache, FetchDirectoryPathForUnknownIndex) +{ + auto value = this->cache.value(41); + + ASSERT_THAT(value, Eq("bar")); +} + +TYPED_TEST(StorageCache, AddCalls) +{ + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))); + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("bar"))); + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("poo"))); + + this->cache.add({"foo", "bar", "poo"}); +} + +TYPED_TEST(StorageCache, AddCallsOnlyForNewValues) +{ + this->cache.add({"foo", "poo"}); + + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("taa"))); + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("bar"))); + + this->cache.add({"foo", "bar", "poo", "taa"}); +} + +TYPED_TEST(StorageCache, GetIdAfterAddingValues) +{ + this->cache.add({"foo", "bar", "poo", "taa"}); + + ASSERT_THAT(this->cache.value(this->cache.id("taa")), Eq("taa")); +} + +TYPED_TEST(StorageCache, GetValueAfterAddingValues) +{ + this->cache.add({"foo", "bar", "poo", "taa"}); + + ASSERT_THAT(this->cache.value(this->cache.id("taa")), Eq("taa")); +} + +TYPED_TEST(StorageCache, GetIdAfterAddingValuesMultipleTimes) +{ + this->cache.add({"foo", "taa"}); + + this->cache.add({"foo", "bar", "poo", "taa"}); + + ASSERT_THAT(this->cache.value(this->cache.id("taa")), Eq("taa")); +} + +TYPED_TEST(StorageCache, GetIdAfterAddingTheSameValuesMultipleTimes) +{ + this->cache.add({"foo", "taa", "poo", "taa", "bar", "taa"}); + + ASSERT_THAT(this->cache.value(this->cache.id("taa")), Eq("taa")); +} + +TYPED_TEST(StorageCache, AddingEmptyValues) +{ + this->cache.add({}); +} + +TYPED_TEST(StorageCache, FetchIdsFromStorageCalls) +{ + InSequence s; + + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + EXPECT_CALL(this->mockMutex, lock()); + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("foo"))); + EXPECT_CALL(this->mockMutex, unlock()); + EXPECT_CALL(this->mockMutex, lock_shared()); + EXPECT_CALL(this->mockMutex, unlock_shared()); + EXPECT_CALL(this->mockMutex, lock()); + EXPECT_CALL(this->mockStorage, fetchDirectoryId(Eq("bar"))); + EXPECT_CALL(this->mockMutex, unlock()); + + this->cache.ids({"foo", "bar"}); +} +} // namespace diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index 5486716bf5d..0f6c0d3bcee 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -68,6 +68,7 @@ SOURCES += \ gtest-qt-printing.cpp \ asynchronousimagecache-test.cpp \ nodelistproperty-test.cpp \ + storagecache-test.cpp \ synchronousimagecache-test.cpp \ imagecachegenerator-test.cpp \ imagecachestorage-test.cpp \