diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index 574e62581a9..be3a1c29554 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -152,7 +152,8 @@ public: auto fetchAllSourceContexts() const { - return selectAllSourceContextsStatement.template rangeWithTransaction(); + return selectAllSourceContextsStatement.template valuesWithTransaction( + 128); } SourceId fetchSourceId(SourceContextId sourceContextId, Utils::SmallStringView sourceName) @@ -191,7 +192,7 @@ public: auto fetchAllSources() const { - return selectAllSourcesStatement.template rangeWithTransaction(); + return selectAllSourcesStatement.template valuesWithTransaction(1024); } SourceId fetchSourceIdUnguarded(SourceContextId sourceContextId, Utils::SmallStringView sourceName) diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h index 20134a5ee37..3fb31f566a6 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h @@ -29,7 +29,7 @@ namespace QmlDesigner { -class NoFilePathForInvalidFilePathId : std::exception +class NoSourcePathForInvalidSourceId : std::exception { public: const char *what() const noexcept override diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h index bb5da21296b..a3a5cc20a19 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h @@ -37,73 +37,72 @@ namespace QmlDesigner { -class FileNameView +class SourceNameView { public: - friend bool operator==(const FileNameView &first, const FileNameView &second) + friend bool operator==(const SourceNameView &first, const SourceNameView &second) noexcept { - return first.sourceContextId == second.sourceContextId && first.fileName == second.fileName; + return first.sourceContextId == second.sourceContextId + && first.sourceName == second.sourceName; } - static int compare(FileNameView first, FileNameView second) noexcept + friend bool operator<(SourceNameView first, SourceNameView second) noexcept { - int directoryDifference = first.sourceContextId.id - second.sourceContextId.id; - - if (directoryDifference) - return directoryDifference; - - return Utils::compare(first.fileName, second.fileName); + return std::tie(first.sourceContextId, first.sourceName) + < std::tie(second.sourceContextId, second.sourceName); } public: - Utils::SmallStringView fileName; + Utils::SmallStringView sourceName; SourceContextId sourceContextId; }; -class FileNameEntry +class SourceNameEntry { public: - FileNameEntry(Utils::SmallStringView fileName, int sourceContextId) - : fileName(fileName) + SourceNameEntry(Utils::SmallStringView sourceName, int sourceContextId) + : sourceName(sourceName) , sourceContextId(sourceContextId) {} - FileNameEntry(Utils::SmallStringView fileName, SourceContextId sourceContextId) - : fileName(fileName) + SourceNameEntry(Utils::SmallStringView sourceName, SourceContextId sourceContextId) + : sourceName(sourceName) , sourceContextId(sourceContextId) {} - FileNameEntry(FileNameView view) - : fileName(view.fileName) + SourceNameEntry(SourceNameView view) + : sourceName(view.sourceName) , sourceContextId(view.sourceContextId) {} - friend bool operator==(const FileNameEntry &first, const FileNameEntry &second) + friend bool operator==(const SourceNameEntry &first, const SourceNameEntry &second) noexcept { - return first.sourceContextId == second.sourceContextId && first.fileName == second.fileName; + return first.sourceContextId == second.sourceContextId + && first.sourceName == second.sourceName; } - friend bool operator!=(const FileNameEntry &first, const FileNameEntry &second) + friend bool operator!=(const SourceNameEntry &first, const SourceNameEntry &second) noexcept { return !(first == second); } - friend bool operator==(const FileNameEntry &first, const FileNameView &second) + friend bool operator==(const SourceNameEntry &first, const SourceNameView &second) noexcept { - return first.sourceContextId == second.sourceContextId && first.fileName == second.fileName; + return first.sourceContextId == second.sourceContextId + && first.sourceName == second.sourceName; } - friend bool operator!=(const FileNameEntry &first, const FileNameView &second) + friend bool operator!=(const SourceNameEntry &first, const SourceNameView &second) noexcept { return !(first == second); } - operator FileNameView() const { return {fileName, sourceContextId}; } + operator SourceNameView() const noexcept { return {sourceName, sourceContextId}; } - operator Utils::SmallString() && { return std::move(fileName); } + operator Utils::SmallString() &&noexcept { return std::move(sourceName); } public: - Utils::SmallString fileName; + Utils::SmallString sourceName; SourceContextId sourceContextId; }; @@ -124,9 +123,9 @@ public: using SourceContexts = std::vector; -class Source : public StorageCacheEntry +class Source : public StorageCacheEntry { - using Base = StorageCacheEntry; + using Base = StorageCacheEntry; public: using Base::Base; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h b/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h index b99ab044902..1a1786b3f11 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/sourcepath.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. diff --git a/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcache.h b/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcache.h new file mode 100644 index 00000000000..42ded75dcd3 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/projectstorage/sourcepathcache.h @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** 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 "projectstorageexceptions.h" +#include "projectstorageids.h" +#include "projectstoragetypes.h" +#include "sourcepath.h" +#include "sourcepathview.h" +#include "storagecache.h" + +#include + +#include + +namespace QmlDesigner { + +template +class SourcePathCache +{ + SourcePathCache(const SourcePathCache &) = default; + SourcePathCache &operator=(const SourcePathCache &) = default; + + template + friend class SourcePathCache; + +public: + SourcePathCache(ProjectStorage &projectStorage) + : m_sourceContextStorageAdapter{projectStorage} + , m_sourceStorageAdapter{projectStorage} + + { + populateIfEmpty(); + } + + SourcePathCache(SourcePathCache &&) = default; + SourcePathCache &operator=(SourcePathCache &&) = default; + + void populateIfEmpty() + { + if (m_sourcePathCache.isEmpty()) { + m_sourceContextPathCache.populate(); + m_sourcePathCache.populate(); + } + } + + SourceId sourceId(SourcePathView sourcePath) const + { + Utils::SmallStringView sourceContextPath = sourcePath.directory(); + + auto sourceContextId = m_sourceContextPathCache.id(sourceContextPath); + + Utils::SmallStringView sourceName = sourcePath.name(); + + return m_sourcePathCache.id({sourceName, sourceContextId}); + } + + SourceContextId sourceContextId(Utils::SmallStringView sourceContextPath) const + { + Utils::SmallStringView path = sourceContextPath.back() == '/' + ? sourceContextPath.mid(0, sourceContextPath.size() - 1) + : sourceContextPath; + + return m_sourceContextPathCache.id(path); + } + + SourcePath sourcePath(SourceId sourceId) const + { + if (Q_UNLIKELY(!sourceId.isValid())) + throw NoSourcePathForInvalidSourceId(); + + auto entry = m_sourcePathCache.value(sourceId); + + Utils::PathString sourceContextPath = m_sourceContextPathCache.value(entry.sourceContextId); + + return SourcePath{sourceContextPath, entry.sourceName}; + } + + Utils::PathString sourceContextPath(SourceContextId sourceContextId) const + { + if (Q_UNLIKELY(!sourceContextId.isValid())) + throw NoSourceContextPathForInvalidSourceContextId(); + + return m_sourceContextPathCache.value(sourceContextId); + } + + SourceContextId sourceContextId(SourceId sourceId) const + { + if (Q_UNLIKELY(!sourceId.isValid())) + throw NoSourcePathForInvalidSourceId(); + + return m_sourcePathCache.value(sourceId).sourceContextId; + } + +private: + class SourceContextStorageAdapter + { + public: + auto fetchId(const Utils::SmallStringView sourceContextPath) + { + return storage.fetchSourceContextId(sourceContextPath); + } + + auto fetchValue(SourceContextId id) { return storage.fetchSourceContextPath(id); } + + auto fetchAll() { return storage.fetchAllSourceContexts(); } + + ProjectStorage &storage; + }; + + class SourceStorageAdapter + { + public: + auto fetchId(SourceNameView sourceNameView) + { + return storage.fetchSourceId(sourceNameView.sourceContextId, sourceNameView.sourceName); + } + + auto fetchValue(SourceId id) + { + auto entry = storage.fetchSourceNameAndSourceContextId(id); + + return SourceNameEntry{std::move(entry.sourceName), entry.sourceContextId}; + } + + auto fetchAll() { return storage.fetchAllSources(); } + + ProjectStorage &storage; + }; + + static bool sourceContextLess(Utils::SmallStringView first, Utils::SmallStringView second) noexcept + { + return Utils::reverseCompare(first, second) < 0; + } + + static bool sourceLess(SourceNameView first, SourceNameView second) noexcept + { + return first < second; + } + + using SourceContextPathCache = StorageCache; + using SourceNameCache + = StorageCache; + +private: + SourceContextStorageAdapter m_sourceContextStorageAdapter; + SourceStorageAdapter m_sourceStorageAdapter; + mutable SourceContextPathCache m_sourceContextPathCache{m_sourceContextStorageAdapter}; + mutable SourceNameCache m_sourcePathCache{m_sourceStorageAdapter}; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/sourcepathview.h b/src/plugins/qmldesigner/designercore/projectstorage/sourcepathview.h index 6689e28c48b..5e0a86d119b 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/sourcepathview.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/sourcepathview.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. diff --git a/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h b/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h index b0aa7c574cf..9ae5ba9c6a1 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/storagecache.h @@ -65,7 +65,7 @@ template> class StorageCache { @@ -80,45 +80,48 @@ class StorageCache StorageCacheIndex(const char *) = delete; - constexpr explicit StorageCacheIndex(int id) + constexpr explicit StorageCacheIndex(int id) noexcept : id{id} {} - constexpr explicit StorageCacheIndex(std::size_t id) + constexpr explicit StorageCacheIndex(std::size_t id) noexcept : id{static_cast(id)} {} - constexpr explicit StorageCacheIndex(std::ptrdiff_t id) + constexpr explicit StorageCacheIndex(std::ptrdiff_t id) noexcept : id{static_cast(id)} {} - constexpr StorageCacheIndex operator=(std::ptrdiff_t newId) + constexpr StorageCacheIndex operator=(std::ptrdiff_t newId) noexcept { id = static_cast(newId); return *this; } - constexpr StorageCacheIndex operator+(int amount) { return StorageCacheIndex{id + amount}; } + constexpr StorageCacheIndex operator+(int amount) const noexcept + { + return StorageCacheIndex{id + amount}; + } - constexpr friend bool operator==(StorageCacheIndex first, StorageCacheIndex second) + constexpr friend bool operator==(StorageCacheIndex first, StorageCacheIndex second) noexcept { return first.id == second.id && first.isValid() && second.isValid(); } - constexpr friend bool operator<(StorageCacheIndex first, StorageCacheIndex second) + constexpr friend bool operator<(StorageCacheIndex first, StorageCacheIndex second) noexcept { return first.id < second.id; } - constexpr friend bool operator>=(StorageCacheIndex first, StorageCacheIndex second) + constexpr friend bool operator>=(StorageCacheIndex first, StorageCacheIndex second) noexcept { return first.id >= second.id; } - constexpr bool isValid() const { return id >= 0; } + constexpr bool isValid() const noexcept { return id >= 0; } - explicit operator std::size_t() const { return static_cast(id); } + explicit operator std::size_t() const noexcept { return static_cast(id); } public: int id = -1; @@ -151,12 +154,12 @@ public: return cache; } - StorageCache(StorageCache &&other) + StorageCache(StorageCache &&other) noexcept : m_entries(std::move(other.m_entries)) , m_indices(std::move(other.m_indices)) {} - StorageCache &operator=(StorageCache &&other) + StorageCache &operator=(StorageCache &&other) noexcept { m_entries = std::move(other.m_entries); m_indices = std::move(other.m_indices); @@ -164,21 +167,21 @@ public: return *this; } - void populate(CacheEntries &&entries) + void populate() { - uncheckedPopulate(std::move(entries)); + uncheckedPopulate(); checkEntries(); } - void uncheckedPopulate(CacheEntries &&entries) + void uncheckedPopulate() { - std::sort(entries.begin(), entries.end(), [](ViewType first, ViewType second) { + m_entries = m_storage.fetchAll(); + + std::sort(m_entries.begin(), m_entries.end(), [](ViewType first, ViewType second) { return compare(first, second); }); - m_entries = std::move(entries); - std::size_t max_id = 0; auto found = std::max_element(m_entries.begin(), diff --git a/src/plugins/qmldesigner/designercore/projectstorage/storagecacheentry.h b/src/plugins/qmldesigner/designercore/projectstorage/storagecacheentry.h index e2557c623c4..9d246af2faf 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/storagecacheentry.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/storagecacheentry.h @@ -41,7 +41,7 @@ public: , id{id} {} - operator ViewType() const { return value; } + operator ViewType() const noexcept { return value; } friend bool operator==(const StorageCacheEntry &first, const StorageCacheEntry &second) { return first.id == second.id && first.value == second.value; diff --git a/src/plugins/qmldesigner/designercore/projectstorage/storagecachefwd.h b/src/plugins/qmldesigner/designercore/projectstorage/storagecachefwd.h index 3c5df91391c..0479353260d 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/storagecachefwd.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/storagecachefwd.h @@ -36,7 +36,7 @@ template class StorageCache; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri index 00282b14915..e25408a7877 100644 --- a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri +++ b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri @@ -81,4 +81,5 @@ HEADERS += \ $$PWD/designercore/projectstorage/projectstorage.h \ $$PWD/designercore/include/projectstorageids.h \ $$PWD/designercore/projectstorage/projectstoragetypes.h \ - $$PWD/designercore/projectstorage/projectstoragesqlitefunctionregistry.h + $$PWD/designercore/projectstorage/projectstoragesqlitefunctionregistry.h \ + $$PWD/designercore/projectstorage/sourcepathcache.h diff --git a/tests/unit/unittest/projectstoragemock.h b/tests/unit/unittest/projectstoragemock.h index 9a4fdec1d88..e54ae043866 100644 --- a/tests/unit/unittest/projectstoragemock.h +++ b/tests/unit/unittest/projectstoragemock.h @@ -54,7 +54,7 @@ public: MOCK_METHOD1(fetchSourceNameAndSourceContextId, QmlDesigner::Sources::SourceNameAndSourceContextId(QmlDesigner::SourceId sourceId)); MOCK_METHOD0(fetchAllSourceContexts, std::vector()); - MOCK_METHOD0(fetchAllSources, std::vector()); + MOCK_METHOD0(fetchAllSources, std::vector()); SqliteDatabaseMock &database() { return databaseMock; } diff --git a/tests/unit/unittest/sourcepathcache-test.cpp b/tests/unit/unittest/sourcepathcache-test.cpp new file mode 100644 index 00000000000..f00a1f58a78 --- /dev/null +++ b/tests/unit/unittest/sourcepathcache-test.cpp @@ -0,0 +1,428 @@ +/**************************************************************************** +** +** 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 "projectstoragemock.h" +#include "sqlitedatabasemock.h" + +#include + +namespace { + +using QmlDesigner::SourceContextId; +using QmlDesigner::SourceId; +using QmlDesigner::SourceIds; +using Cache = QmlDesigner::SourcePathCache>; +using NFP = QmlDesigner::SourcePath; +using QmlDesigner::SourcePathView; +using QmlDesigner::SourcePathViews; +using QmlDesigner::Sources::SourceNameAndSourceContextId; + +class SourcePathCache : public testing::Test +{ +protected: + SourcePathCache() + { + ON_CALL(storageMock, fetchSourceContextId(Eq("/path/to"))) + .WillByDefault(Return(SourceContextId{5})); + ON_CALL(storageMock, fetchSourceContextId(Eq("/path2/to"))) + .WillByDefault(Return(SourceContextId{6})); + ON_CALL(storageMock, fetchSourceId(SourceContextId{5}, Eq("file.cpp"))) + .WillByDefault(Return(SourceId{42})); + ON_CALL(storageMock, fetchSourceId(SourceContextId{5}, Eq("file2.cpp"))) + .WillByDefault(Return(SourceId{63})); + ON_CALL(storageMock, fetchSourceId(SourceContextId{6}, Eq("file.cpp"))) + .WillByDefault(Return(SourceId{72})); + ON_CALL(storageMock, fetchSourceContextPath(SourceContextId{5})) + .WillByDefault(Return(Utils::PathString("/path/to"))); + ON_CALL(storageMock, fetchSourceNameAndSourceContextId(SourceId{42})) + .WillByDefault(Return(SourceNameAndSourceContextId("file.cpp", SourceContextId{5}))); + ON_CALL(storageMockFilled, fetchAllSources()) + .WillByDefault(Return(std::vector({ + {"file.cpp", SourceContextId{6}, SourceId{72}}, + {"file2.cpp", SourceContextId{5}, SourceId{63}}, + {"file.cpp", SourceContextId{5}, SourceId{42}}, + }))); + ON_CALL(storageMockFilled, fetchAllSourceContexts()) + .WillByDefault(Return(std::vector( + {{"/path2/to", SourceContextId{6}}, {"/path/to", SourceContextId{5}}}))); + ON_CALL(storageMockFilled, fetchSourceContextId(Eq("/path/to"))) + .WillByDefault(Return(SourceContextId{5})); + ON_CALL(storageMockFilled, fetchSourceId(SourceContextId{5}, Eq("file.cpp"))) + .WillByDefault(Return(SourceId{42})); + } + +protected: + NiceMock databaseMock; + NiceMock storageMock{databaseMock}; + Cache cache{storageMock}; + NiceMock storageMockFilled{databaseMock}; + Cache cacheNotFilled{storageMockFilled}; +}; + +TEST_F(SourcePathCache, SourceIdWithOutAnyEntryCallSourceContextId) +{ + EXPECT_CALL(storageMock, fetchSourceContextId(Eq("/path/to"))); + + cache.sourceId(SourcePathView("/path/to/file.cpp")); +} + +TEST_F(SourcePathCache, SourceIdWithOutAnyEntryCalls) +{ + EXPECT_CALL(storageMock, fetchSourceId(SourceContextId{5}, Eq("file.cpp"))); + + cache.sourceId(SourcePathView("/path/to/file.cpp")); +} + +TEST_F(SourcePathCache, SourceIdOfSourceIdWithOutAnyEntry) +{ + auto sourceId = cache.sourceId(SourcePathView("/path/to/file.cpp")); + + ASSERT_THAT(sourceId, SourceId{42}); +} + +TEST_F(SourcePathCache, IfEntryExistsDontCallInStrorage) +{ + cache.sourceId(SourcePathView("/path/to/file.cpp")); + + EXPECT_CALL(storageMock, fetchSourceContextId(Eq("/path/to"))).Times(0); + EXPECT_CALL(storageMock, fetchSourceId(SourceContextId{5}, Eq("file.cpp"))).Times(0); + + cache.sourceId(SourcePathView("/path/to/file.cpp")); +} + +TEST_F(SourcePathCache, IfDirectoryEntryExistsDontCallFetchSourceContextIdButStillCallFetchSourceId) +{ + cache.sourceId(SourcePathView("/path/to/file2.cpp")); + + EXPECT_CALL(storageMock, fetchSourceContextId(Eq("/path/to"))).Times(0); + EXPECT_CALL(storageMock, fetchSourceId(SourceContextId{5}, Eq("file.cpp"))); + + cache.sourceId(SourcePathView("/path/to/file.cpp")); +} + +TEST_F(SourcePathCache, GetSourceIdWithCachedValue) +{ + cache.sourceId(SourcePathView("/path/to/file.cpp")); + + auto sourceId = cache.sourceId(SourcePathView("/path/to/file.cpp")); + + ASSERT_THAT(sourceId, SourceId{42}); +} + +TEST_F(SourcePathCache, GetSourceIdWithSourceContextIdCached) +{ + cache.sourceId(SourcePathView("/path/to/file.cpp")); + + auto sourceId = cache.sourceId(SourcePathView("/path/to/file2.cpp")); + + ASSERT_THAT(sourceId, SourceId{63}); +} + +TEST_F(SourcePathCache, ThrowForGettingAFilePathWithAnInvalidId) +{ + SourceId sourceId; + + ASSERT_THROW(cache.sourcePath(sourceId), QmlDesigner::NoSourcePathForInvalidSourceId); +} + +TEST_F(SourcePathCache, GetAFilePath) +{ + SourceId sourceId = cache.sourceId(SourcePathView("/path/to/file.cpp")); + + auto sourcePath = cache.sourcePath(sourceId); + + ASSERT_THAT(sourcePath, Eq(SourcePathView{"/path/to/file.cpp"})); +} + +TEST_F(SourcePathCache, GetAFilePathWithCachedSourceId) +{ + SourceId sourceId{42}; + + auto sourcePath = cache.sourcePath(sourceId); + + ASSERT_THAT(sourcePath, Eq(SourcePathView{"/path/to/file.cpp"})); +} + +TEST_F(SourcePathCache, FileNamesAreUniqueForEveryDirectory) +{ + SourceId sourceId = cache.sourceId(SourcePathView("/path/to/file.cpp")); + + SourceId sourcePath2Id = cache.sourceId(SourcePathView("/path2/to/file.cpp")); + + ASSERT_THAT(sourcePath2Id, Ne(sourceId)); +} + +TEST_F(SourcePathCache, DuplicateFilePathsAreEqual) +{ + SourceId sourcePath1Id = cache.sourceId(SourcePathView("/path/to/file.cpp")); + + SourceId sourcePath2Id = cache.sourceId(SourcePathView("/path/to/file.cpp")); + + ASSERT_THAT(sourcePath2Id, Eq(sourcePath1Id)); +} + +TEST_F(SourcePathCache, SourceContextIdCallsFetchSourceContextId) +{ + EXPECT_CALL(storageMock, fetchSourceContextId(Eq("/path/to"))); + + cache.sourceContextId(Utils::SmallString("/path/to")); +} + +TEST_F(SourcePathCache, SecondSourceContextIdCallsNotFetchSourceContextId) +{ + cache.sourceContextId(Utils::SmallString("/path/to")); + + EXPECT_CALL(storageMock, fetchSourceContextId(Eq("/path/to"))).Times(0); + + cache.sourceContextId(Utils::SmallString("/path/to")); +} + +TEST_F(SourcePathCache, SourceContextIdWithTrailingSlash) +{ + EXPECT_CALL(storageMock, fetchSourceContextId(Eq("/path/to"))); + + cache.sourceContextId(Utils::SmallString("/path/to/")); +} + +TEST_F(SourcePathCache, SourceContextId) +{ + auto id = cache.sourceContextId(Utils::SmallString("/path/to")); + + ASSERT_THAT(id, Eq(SourceContextId{5})); +} + +TEST_F(SourcePathCache, SourceContextIdIsAlreadyInCache) +{ + auto firstId = cache.sourceContextId(Utils::SmallString("/path/to")); + + auto secondId = cache.sourceContextId(Utils::SmallString("/path/to")); + + ASSERT_THAT(secondId, firstId); +} + +TEST_F(SourcePathCache, SourceContextIdIsAlreadyInCacheWithTrailingSlash) +{ + auto firstId = cache.sourceContextId(Utils::SmallString("/path/to/")); + + auto secondId = cache.sourceContextId(Utils::SmallString("/path/to/")); + + ASSERT_THAT(secondId, firstId); +} + +TEST_F(SourcePathCache, SourceContextIdIsAlreadyInCacheWithAndWithoutTrailingSlash) +{ + auto firstId = cache.sourceContextId(Utils::SmallString("/path/to/")); + + auto secondId = cache.sourceContextId(Utils::SmallString("/path/to")); + + ASSERT_THAT(secondId, firstId); +} + +TEST_F(SourcePathCache, SourceContextIdIsAlreadyInCacheWithoutAndWithTrailingSlash) +{ + auto firstId = cache.sourceContextId(Utils::SmallString("/path/to")); + + auto secondId = cache.sourceContextId(Utils::SmallString("/path/to/")); + + ASSERT_THAT(secondId, firstId); +} + +TEST_F(SourcePathCache, ThrowForGettingADirectoryPathWithAnInvalidId) +{ + SourceContextId sourceContextId; + + ASSERT_THROW(cache.sourceContextPath(sourceContextId), + QmlDesigner::NoSourceContextPathForInvalidSourceContextId); +} + +TEST_F(SourcePathCache, GetADirectoryPath) +{ + SourceContextId sourceContextId{5}; + + auto sourceContextPath = cache.sourceContextPath(sourceContextId); + + ASSERT_THAT(sourceContextPath, Eq(Utils::SmallStringView{"/path/to"})); +} + +TEST_F(SourcePathCache, GetADirectoryPathWithCachedSourceContextId) +{ + SourceContextId sourceContextId{5}; + cache.sourceContextPath(sourceContextId); + + auto sourceContextPath = cache.sourceContextPath(sourceContextId); + + ASSERT_THAT(sourceContextPath, Eq(Utils::SmallStringView{"/path/to"})); +} + +TEST_F(SourcePathCache, DirectoryPathCallsFetchDirectoryPath) +{ + EXPECT_CALL(storageMock, fetchSourceContextPath(Eq(SourceContextId{5}))); + + cache.sourceContextPath(SourceContextId{5}); +} + +TEST_F(SourcePathCache, SecondDirectoryPathCallsNotFetchDirectoryPath) +{ + cache.sourceContextPath(SourceContextId{5}); + + EXPECT_CALL(storageMock, fetchSourceContextPath(_)).Times(0); + + cache.sourceContextPath(SourceContextId{5}); +} + +TEST_F(SourcePathCache, ThrowForGettingASourceContextIdWithAnInvalidSourceId) +{ + SourceId sourceId; + + ASSERT_THROW(cache.sourceContextId(sourceId), QmlDesigner::NoSourcePathForInvalidSourceId); +} + +TEST_F(SourcePathCache, FetchSourceContextIdBySourceId) +{ + auto sourceContextId = cache.sourceContextId(SourceId{42}); + + ASSERT_THAT(sourceContextId, Eq(SourceContextId{5})); +} + +TEST_F(SourcePathCache, FetchSourceContextIdBySourceIdCached) +{ + cache.sourceContextId(SourceId{42}); + + auto sourceContextId = cache.sourceContextId(SourceId{42}); + + ASSERT_THAT(sourceContextId, Eq(SourceContextId{5})); +} + +TEST_F(SourcePathCache, FetchFilePathAfterFetchingSourceContextIdBySourceId) +{ + cache.sourceContextId(SourceId{42}); + + auto sourcePath = cache.sourcePath(SourceId{42}); + + ASSERT_THAT(sourcePath, Eq("/path/to/file.cpp")); +} + +TEST_F(SourcePathCache, FetchSourceContextIdAfterFetchingFilePathBySourceId) +{ + cache.sourcePath(SourceId{42}); + + auto sourceContextId = cache.sourceContextId(SourceId{42}); + + ASSERT_THAT(sourceContextId, Eq(SourceContextId{5})); +} + +TEST_F(SourcePathCache, FetchAllSourceContextsAndSourcesAtCreation) +{ + EXPECT_CALL(storageMock, fetchAllSourceContexts()); + EXPECT_CALL(storageMock, fetchAllSources()); + + Cache cache{storageMock}; +} + +TEST_F(SourcePathCache, GetFileIdInFilledCache) +{ + Cache cacheFilled{storageMockFilled}; + + auto id = cacheFilled.sourceId("/path2/to/file.cpp"); + + ASSERT_THAT(id, Eq(SourceId{72})); +} + +TEST_F(SourcePathCache, GetSourceContextIdInFilledCache) +{ + Cache cacheFilled{storageMockFilled}; + + auto id = cacheFilled.sourceContextId(SourceId{42}); + + ASSERT_THAT(id, Eq(SourceContextId{5})); +} + +TEST_F(SourcePathCache, GetDirectoryPathInFilledCache) +{ + Cache cacheFilled{storageMockFilled}; + + auto path = cacheFilled.sourceContextPath(SourceContextId{5}); + + ASSERT_THAT(path, Eq("/path/to")); +} + +TEST_F(SourcePathCache, GetFilePathInFilledCache) +{ + Cache cacheFilled{storageMockFilled}; + + auto path = cacheFilled.sourcePath(SourceId{42}); + + ASSERT_THAT(path, Eq("/path/to/file.cpp")); +} + +TEST_F(SourcePathCache, GetFileIdInAfterPopulateIfEmpty) +{ + cacheNotFilled.populateIfEmpty(); + + auto id = cacheNotFilled.sourceId("/path2/to/file.cpp"); + + ASSERT_THAT(id, Eq(SourceId{72})); +} + +TEST_F(SourcePathCache, DontPopulateIfNotEmpty) +{ + cacheNotFilled.sourceId("/path/to/file.cpp"); + + EXPECT_CALL(storageMockFilled, fetchAllSourceContexts()).Times(0); + EXPECT_CALL(storageMockFilled, fetchAllSources()).Times(0); + + cacheNotFilled.populateIfEmpty(); +} + +TEST_F(SourcePathCache, GetSourceContextIdAfterPopulateIfEmpty) +{ + cacheNotFilled.populateIfEmpty(); + + auto id = cacheNotFilled.sourceContextId(SourceId{42}); + + ASSERT_THAT(id, Eq(SourceContextId{5})); +} + +TEST_F(SourcePathCache, GetDirectoryPathAfterPopulateIfEmpty) +{ + cacheNotFilled.populateIfEmpty(); + + auto path = cacheNotFilled.sourceContextPath(SourceContextId{5}); + + ASSERT_THAT(path, Eq("/path/to")); +} + +TEST_F(SourcePathCache, GetFilePathAfterPopulateIfEmptye) +{ + cacheNotFilled.populateIfEmpty(); + + auto path = cacheNotFilled.sourcePath(SourceId{42}); + + ASSERT_THAT(path, Eq("/path/to/file.cpp")); +} + +} // namespace diff --git a/tests/unit/unittest/storagecache-test.cpp b/tests/unit/unittest/storagecache-test.cpp index 537918e5bc8..2be79de2a83 100644 --- a/tests/unit/unittest/storagecache-test.cpp +++ b/tests/unit/unittest/storagecache-test.cpp @@ -44,9 +44,11 @@ using Utils::reverseCompare; class StorageAdapter { public: - auto fetchId(Utils::SmallStringView view) { return storage.fetchSourceContextId(view); } + auto fetchId(Utils::SmallStringView view) const { return storage.fetchSourceContextId(view); } - auto fetchValue(SourceContextId id) { return storage.fetchSourceContextPath(id); } + auto fetchValue(SourceContextId id) const { return storage.fetchSourceContextPath(id); } + + auto fetchAll() const { return storage.fetchAllSourceContexts(); } ProjectStorageMock &storage; }; @@ -56,15 +58,21 @@ auto less(Utils::SmallStringView first, Utils::SmallStringView second) -> bool return Utils::reverseCompare(first, second) < 0; }; -using CacheWithMockLocking = QmlDesigner:: - StorageCache, less>; +using CacheWithMockLocking = QmlDesigner::StorageCache, + less, + QmlDesigner::Sources::SourceContext>; using CacheWithoutLocking = QmlDesigner::StorageCache, - less>; + less, + QmlDesigner::Sources::SourceContext>; template class StorageCache : public testing::Test @@ -223,9 +231,7 @@ TYPED_TEST(StorageCache, IsNotEmpty) TYPED_TEST(StorageCache, PopulateWithEmptyVector) { - typename TypeParam::CacheEntries entries; - - this->cache.uncheckedPopulate(std::move(entries)); + this->cache.uncheckedPopulate(); ASSERT_TRUE(this->cache.isEmpty()); } @@ -236,8 +242,9 @@ TYPED_TEST(StorageCache, IsNotEmptyAfterPopulateWithSomeEntries) {this->filePath2.clone(), this->id4}, {this->filePath3.clone(), this->id3}, {this->filePath4.clone(), SourceContextId{5}}}; + ON_CALL(this->mockStorage, fetchAllSourceContexts()).WillByDefault(Return(entries)); - this->cache.uncheckedPopulate(std::move(entries)); + this->cache.uncheckedPopulate(); ASSERT_TRUE(!this->cache.isEmpty()); } @@ -248,7 +255,8 @@ TYPED_TEST(StorageCache, GetEntryAfterPopulateWithSomeEntries) {this->filePath2.clone(), this->id2}, {this->filePath3.clone(), SourceContextId{7}}, {this->filePath4.clone(), this->id4}}; - this->cache.uncheckedPopulate(std::move(entries)); + ON_CALL(this->mockStorage, fetchAllSourceContexts()).WillByDefault(Return(entries)); + this->cache.uncheckedPopulate(); auto value = this->cache.value(SourceContextId{7}); @@ -261,8 +269,9 @@ TYPED_TEST(StorageCache, EntriesHaveUniqueIds) {this->filePath2.clone(), this->id2}, {this->filePath3.clone(), this->id3}, {this->filePath4.clone(), this->id3}}; + ON_CALL(this->mockStorage, fetchAllSourceContexts()).WillByDefault(Return(entries)); - ASSERT_THROW(this->cache.populate(std::move(entries)), StorageCacheException); + ASSERT_THROW(this->cache.populate(), StorageCacheException); } TYPED_TEST(StorageCache, MultipleEntries) @@ -271,8 +280,9 @@ TYPED_TEST(StorageCache, MultipleEntries) {this->filePath1.clone(), this->id2}, {this->filePath3.clone(), this->id3}, {this->filePath4.clone(), this->id4}}; + ON_CALL(this->mockStorage, fetchAllSourceContexts()).WillByDefault(Return(entries)); - ASSERT_THROW(this->cache.populate(std::move(entries)), StorageCacheException); + ASSERT_THROW(this->cache.populate(), StorageCacheException); } TYPED_TEST(StorageCache, IdIsReadAndWriteLockedForUnknownEntry) diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index 91a9dd36aef..e6d33b6cbc0 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -149,7 +149,8 @@ SOURCES += \ sqlitereadwritestatementmock.cpp \ sourcepath-test.cpp \ sourcepathview-test.cpp \ - projectstorage-test.cpp + projectstorage-test.cpp \ + sourcepathcache-test.cpp !isEmpty(QTC_UNITTEST_BUILD_CPP_PARSER):SOURCES += matchingtext-test.cpp