diff --git a/src/libs/sqlite/sqlitetimestamp.h b/src/libs/sqlite/sqlitetimestamp.h index f30d71fb299..c7c100ec454 100644 --- a/src/libs/sqlite/sqlitetimestamp.h +++ b/src/libs/sqlite/sqlitetimestamp.h @@ -40,6 +40,12 @@ public: return first.value == second.value; } + friend bool operator!=(TimeStamp first, TimeStamp second) { return !(first == second); } + + bool isValid() const { return value >= 0; } + + long long operator*() { return value; } + public: long long value = -1; }; diff --git a/src/libs/utils/variant.h b/src/libs/utils/variant.h index aaaa78e8f85..2f118e6b4c9 100644 --- a/src/libs/utils/variant.h +++ b/src/libs/utils/variant.h @@ -39,6 +39,7 @@ namespace Utils { using std::get; using std::get_if; using std::holds_alternative; +using std::monostate; using std::variant; using std::variant_alternative_t; using std::visit; @@ -51,6 +52,7 @@ namespace Utils { using mpark::get; using mpark::get_if; using mpark::holds_alternative; +using mpark::monostate; using mpark::variant; using mpark::variant_alternative_t; using mpark::visit; diff --git a/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.cpp b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.cpp new file mode 100644 index 00000000000..4314700c4b3 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 "asynchronousimagefactory.h" + +#include "imagecachegenerator.h" +#include "imagecachestorage.h" +#include "timestampprovider.h" + +namespace QmlDesigner { + +AsynchronousImageFactory::AsynchronousImageFactory(ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider) + : m_storage(storage) + , m_generator(generator) + , m_timeStampProvider(timeStampProvider) +{ + m_backgroundThread = std::thread{[this] { + while (isRunning()) { + if (auto entry = getEntry(); entry) { + request(entry->name, + entry->extraId, + std::move(entry->auxiliaryData), + m_storage, + m_generator, + m_timeStampProvider); + } + + waitForEntries(); + } + }}; +} + +AsynchronousImageFactory::~AsynchronousImageFactory() +{ + clean(); + wait(); +} + +void AsynchronousImageFactory::generate(Utils::PathString name, + Utils::SmallString extraId, + ImageCache::AuxiliaryData auxiliaryData) +{ + addEntry(std::move(name), std::move(extraId), std::move(auxiliaryData)); + m_condition.notify_all(); +} + +void AsynchronousImageFactory::addEntry(Utils::PathString &&name, + Utils::SmallString &&extraId, + ImageCache::AuxiliaryData &&auxiliaryData) +{ + std::unique_lock lock{m_mutex}; + + m_entries.emplace_back(std::move(name), std::move(extraId), std::move(auxiliaryData)); +} + +bool AsynchronousImageFactory::isRunning() +{ + std::unique_lock lock{m_mutex}; + return !m_finishing || m_entries.size(); +} + +void AsynchronousImageFactory::waitForEntries() +{ + std::unique_lock lock{m_mutex}; + if (m_entries.empty()) + m_condition.wait(lock, [&] { return m_entries.size() || m_finishing; }); +} + +std::optional AsynchronousImageFactory::getEntry() +{ + std::unique_lock lock{m_mutex}; + + if (m_entries.empty()) + return {}; + + Entry entry = m_entries.front(); + m_entries.pop_front(); + + return {entry}; +} + +void AsynchronousImageFactory::request(Utils::SmallStringView name, + Utils::SmallStringView extraId, + ImageCache::AuxiliaryData auxiliaryData, + ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider) +{ + const auto id = extraId.empty() ? Utils::PathString{name} + : Utils::PathString::join({name, "+", extraId}); + + const auto currentModifiedTime = timeStampProvider.timeStamp(name); + const auto storageModifiedTime = storage.fetchModifiedImageTime(id); + + if (currentModifiedTime != storageModifiedTime) { + generator.generateImage(name, + extraId, + currentModifiedTime, + ImageCache::CaptureImageWithSmallImageCallback{}, + ImageCache::AbortCallback{}, + std::move(auxiliaryData)); + } +} + +void AsynchronousImageFactory::clean() +{ + clearEntries(); + m_generator.clean(); +} + +void AsynchronousImageFactory::wait() +{ + stopThread(); + m_condition.notify_all(); + if (m_backgroundThread.joinable()) + m_backgroundThread.join(); +} + +void AsynchronousImageFactory::clearEntries() +{ + std::unique_lock lock{m_mutex}; + m_entries.clear(); +} + +void AsynchronousImageFactory::stopThread() +{ + std::unique_lock lock{m_mutex}; + m_finishing = true; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.h b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.h new file mode 100644 index 00000000000..8ec280ac482 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/asynchronousimagefactory.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 "imagecacheauxiliarydata.h" + +#include + +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +class TimeStampProviderInterface; +class ImageCacheStorageInterface; +class ImageCacheGeneratorInterface; +class ImageCacheCollectorInterface; + +class AsynchronousImageFactory +{ +public: + AsynchronousImageFactory(ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider); + + ~AsynchronousImageFactory(); + + void generate(Utils::PathString name, + Utils::SmallString extraId = {}, + ImageCache::AuxiliaryData auxiliaryData = {}); + void clean(); + +private: + struct Entry + { + Entry() = default; + Entry(Utils::PathString name, + Utils::SmallString extraId, + ImageCache::AuxiliaryData &&auxiliaryData) + : name{std::move(name)} + , extraId{std::move(extraId)} + , auxiliaryData{std::move(auxiliaryData)} + {} + + Utils::PathString name; + Utils::SmallString extraId; + ImageCache::AuxiliaryData auxiliaryData; + }; + + void addEntry(Utils::PathString &&name, + Utils::SmallString &&extraId, + ImageCache::AuxiliaryData &&auxiliaryData); + bool isRunning(); + void waitForEntries(); + std::optional getEntry(); + void request(Utils::SmallStringView name, + Utils::SmallStringView extraId, + ImageCache::AuxiliaryData auxiliaryData, + ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider); + void wait(); + void clearEntries(); + void stopThread(); + +private: + std::deque m_entries; + std::mutex m_mutex; + std::condition_variable m_condition; + std::thread m_backgroundThread; + ImageCacheStorageInterface &m_storage; + ImageCacheGeneratorInterface &m_generator; + TimeStampProviderInterface &m_timeStampProvider; + bool m_finishing{false}; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h index b956a42217e..c630a5c4775 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h @@ -160,6 +160,11 @@ public: } } + Sqlite::TimeStamp fetchModifiedImageTime(Utils::SmallStringView name) const override + { + return selectModifiedImageTimeStatement.template valueWithTransaction(name); + } + private: class Initializer { @@ -289,6 +294,8 @@ public: "INSERT INTO icons(name, mtime, icon) VALUES (?1, ?2, ?3) ON " "CONFLICT(name) DO UPDATE SET mtime=excluded.mtime, icon=excluded.icon", database}; + mutable ReadStatement<1, 1> selectModifiedImageTimeStatement{ + "SELECT mtime FROM images WHERE name=?1", database}; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h index bf0c1d62485..80ade8da03f 100644 --- a/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h @@ -52,6 +52,7 @@ public: = 0; virtual void storeIcon(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QIcon &icon) = 0; virtual void walCheckpointFull() = 0; + virtual Sqlite::TimeStamp fetchModifiedImageTime(Utils::SmallStringView name) const = 0; protected: ~ImageCacheStorageInterface() = default; diff --git a/src/plugins/qmldesigner/designercore/include/imagecacheauxiliarydata.h b/src/plugins/qmldesigner/designercore/include/imagecacheauxiliarydata.h index c8c4d115878..ae87681626a 100644 --- a/src/plugins/qmldesigner/designercore/include/imagecacheauxiliarydata.h +++ b/src/plugins/qmldesigner/designercore/include/imagecacheauxiliarydata.h @@ -38,9 +38,6 @@ namespace QmlDesigner { namespace ImageCache { -class NullAuxiliaryData -{}; - class FontCollectorSizeAuxiliaryData { public: @@ -57,7 +54,7 @@ public: QString text; }; -using AuxiliaryData = Utils::variant; +using AuxiliaryData = Utils::variant; enum class AbortReason : char { Abort, Failed }; diff --git a/src/plugins/qmldesigner/qmldesignercore.cmake b/src/plugins/qmldesigner/qmldesignercore.cmake index d31f1c4ba4a..3332a532bee 100644 --- a/src/plugins/qmldesigner/qmldesignercore.cmake +++ b/src/plugins/qmldesigner/qmldesignercore.cmake @@ -109,6 +109,8 @@ function(extend_with_qmldesigner_core target_name) imagecache/asynchronousexplicitimagecache.cpp imagecache/asynchronousimagecache.cpp + imagecache/asynchronousimagefactory.cpp + imagecache/asynchronousimagefactory.h imagecache/imagecachecollector.cpp imagecache/imagecachecollector.h imagecache/imagecachecollectorinterface.h diff --git a/tests/unit/unittest/CMakeLists.txt b/tests/unit/unittest/CMakeLists.txt index c6cc9615c7d..9642c8fa638 100644 --- a/tests/unit/unittest/CMakeLists.txt +++ b/tests/unit/unittest/CMakeLists.txt @@ -115,6 +115,7 @@ add_qtc_test(unittest GTEST mockimagecachegenerator.h mockimagecachestorage.h asynchronousexplicitimagecache-test.cpp + asynchronousimagefactory-test.cpp ) if (NOT TARGET unittest) @@ -284,6 +285,8 @@ extend_qtc_test(unittest exceptions/rewritingexception.cpp imagecache/asynchronousexplicitimagecache.cpp imagecache/asynchronousimagecache.cpp + imagecache/asynchronousimagefactory.cpp + imagecache/asynchronousimagefactory.h imagecache/imagecachecollectorinterface.h imagecache/imagecachegenerator.cpp imagecache/imagecachegenerator.h diff --git a/tests/unit/unittest/asynchronousexplicitimagecache-test.cpp b/tests/unit/unittest/asynchronousexplicitimagecache-test.cpp index 30fb065ac61..0acaf07e010 100644 --- a/tests/unit/unittest/asynchronousexplicitimagecache-test.cpp +++ b/tests/unit/unittest/asynchronousexplicitimagecache-test.cpp @@ -25,9 +25,7 @@ #include "googletest.h" -#include "mockimagecachegenerator.h" #include "mockimagecachestorage.h" -#include "mocktimestampprovider.h" #include "notification.h" #include diff --git a/tests/unit/unittest/asynchronousimagecache-test.cpp b/tests/unit/unittest/asynchronousimagecache-test.cpp index 5f96b58ba59..fab158c0dca 100644 --- a/tests/unit/unittest/asynchronousimagecache-test.cpp +++ b/tests/unit/unittest/asynchronousimagecache-test.cpp @@ -115,7 +115,7 @@ TEST_F(AsynchronousImageCache, RequestImageRequestImageFromGenerator) EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto &&callback, auto, auto) { notification.notify(); }); + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); cache.requestImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -221,7 +221,7 @@ TEST_F(AsynchronousImageCache, RequestSmallImageRequestImageFromGenerator) EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, Eq(Sqlite::TimeStamp{123}), _, _, _)) - .WillRepeatedly([&](auto, auto, auto, auto &&callback, auto, auto) { notification.notify(); }); + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); cache.requestSmallImage("/path/to/Component.qml", mockCaptureCallback.AsStdFunction(), @@ -284,9 +284,7 @@ TEST_F(AsynchronousImageCache, CleanRemovesEntries) TEST_F(AsynchronousImageCache, CleanCallsAbort) { ON_CALL(mockGenerator, generateImage(_, _, _, _, _, _)) - .WillByDefault([&](auto, auto, auto, auto &&mockCaptureCallback, auto &&, auto) { - waitInThread.wait(); - }); + .WillByDefault([&](auto, auto, auto, auto, auto &&, auto) { waitInThread.wait(); }); cache.requestSmallImage("/path/to/Component1.qml", mockCaptureCallback.AsStdFunction(), mockAbortCallback.AsStdFunction()); diff --git a/tests/unit/unittest/asynchronousimagefactory-test.cpp b/tests/unit/unittest/asynchronousimagefactory-test.cpp new file mode 100644 index 00000000000..0230850cfd8 --- /dev/null +++ b/tests/unit/unittest/asynchronousimagefactory-test.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 "mockimagecachegenerator.h" +#include "mockimagecachestorage.h" +#include "mocktimestampprovider.h" +#include "notification.h" + +#include + +namespace { + +using QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData; + +class AsynchronousImageFactory : public testing::Test +{ +protected: + Notification notification; + Notification waitInThread; + NiceMock mockStorage; + NiceMock mockGenerator; + NiceMock mockTimeStampProvider; + QmlDesigner::AsynchronousImageFactory factory{mockStorage, mockGenerator, mockTimeStampProvider}; + QImage image1{10, 10, QImage::Format_ARGB32}; + QImage smallImage1{1, 1, QImage::Format_ARGB32}; +}; + +TEST_F(AsynchronousImageFactory, RequestImageRequestImageFromGenerator) +{ + ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml"))) + .WillByDefault(Return(Sqlite::TimeStamp{123})); + + EXPECT_CALL(mockGenerator, + generateImage(Eq("/path/to/Component.qml"), + IsEmpty(), + Eq(Sqlite::TimeStamp{123}), + _, + _, + VariantWith(Utils::monostate{}))) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + + factory.generate("/path/to/Component.qml"); + notification.wait(); +} + +TEST_F(AsynchronousImageFactory, RequestImageWithExtraIdRequestImageFromGenerator) +{ + ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml"))) + .WillByDefault(Return(Sqlite::TimeStamp{123})); + + EXPECT_CALL(mockGenerator, + generateImage(Eq("/path/to/Component.qml"), + Eq("foo"), + Eq(Sqlite::TimeStamp{123}), + _, + _, + VariantWith(Utils::monostate{}))) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + + factory.generate("/path/to/Component.qml", "foo"); + notification.wait(); +} + +TEST_F(AsynchronousImageFactory, RequestImageWithAuxiliaryDataRequestImageFromGenerator) +{ + std::vector sizes{{20, 11}}; + ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml"))) + .WillByDefault(Return(Sqlite::TimeStamp{123})); + + EXPECT_CALL(mockGenerator, + generateImage(Eq("/path/to/Component.qml"), + Eq("foo"), + Eq(Sqlite::TimeStamp{123}), + _, + _, + VariantWith(AllOf( + Field(&FontCollectorSizesAuxiliaryData::sizes, + ElementsAre(QSize{20, 11})), + Field(&FontCollectorSizesAuxiliaryData::colorName, Eq(u"color")), + Field(&FontCollectorSizesAuxiliaryData::text, Eq(u"some text")))))) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + + factory.generate("/path/to/Component.qml", + "foo", + QmlDesigner::ImageCache::FontCollectorSizesAuxiliaryData{sizes, + "color", + "some text"}); + notification.wait(); +} + +TEST_F(AsynchronousImageFactory, DontRequestImageRequestImageFromGeneratorIfFileWasNotUpdated) +{ + ON_CALL(mockStorage, fetchModifiedImageTime(Eq("/path/to/Component.qml"))) + .WillByDefault(Return(Sqlite::TimeStamp{124})); + ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml"))) + .WillByDefault(Return(Sqlite::TimeStamp{124})); + + EXPECT_CALL(mockGenerator, generateImage(_, _, _, _, _, _)).Times(0); + + factory.generate("/path/to/Component.qml"); +} + +TEST_F(AsynchronousImageFactory, CleanRemovesEntries) +{ + EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component1.qml"), _, _, _, _, _)) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { waitInThread.wait(); }); + factory.generate("/path/to/Component1.qml"); + + EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component3.qml"), _, _, _, _, _)).Times(0); + + factory.generate("/path/to/Component3.qml"); + factory.clean(); + waitInThread.notify(); +} + +TEST_F(AsynchronousImageFactory, CleanCallsGeneratorClean) +{ + EXPECT_CALL(mockGenerator, clean()).Times(AtLeast(1)); + + factory.clean(); +} + +TEST_F(AsynchronousImageFactory, AfterCleanNewJobsWorks) +{ + factory.clean(); + ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml"))) + .WillByDefault(Return(Sqlite::TimeStamp{123})); + + EXPECT_CALL(mockGenerator, + generateImage(Eq("/path/to/Component.qml"), + IsEmpty(), + Eq(Sqlite::TimeStamp{123}), + _, + _, + VariantWith(Utils::monostate{}))) + .WillRepeatedly([&](auto, auto, auto, auto, auto, auto) { notification.notify(); }); + + factory.generate("/path/to/Component.qml"); + notification.wait(); +} + +} // namespace diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index 8439e5ac0fd..9d90fdb3e0f 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -363,6 +363,11 @@ std::ostream &operator<<(std::ostream &out, Operation operation) return out << toText(operation); } +std::ostream &operator<<(std::ostream &out, TimeStamp timeStamp) +{ + return out << timeStamp.value; +} + namespace SessionChangeSetInternal { namespace { diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h index afd9c62b527..7e89dca0481 100644 --- a/tests/unit/unittest/gtest-creator-printing.h +++ b/tests/unit/unittest/gtest-creator-printing.h @@ -47,12 +47,14 @@ class ValueView; class SessionChangeSet; enum class Operation : char; enum class LockingMode : char; +class TimeStamp; std::ostream &operator<<(std::ostream &out, const Value &value); std::ostream &operator<<(std::ostream &out, const ValueView &value); std::ostream &operator<<(std::ostream &out, Operation operation); std::ostream &operator<<(std::ostream &out, const SessionChangeSet &changeset); std::ostream &operator<<(std::ostream &out, LockingMode lockingMode); +std::ostream &operator<<(std::ostream &out, TimeStamp timeStamp); namespace SessionChangeSetInternal { class ConstIterator; diff --git a/tests/unit/unittest/imagecachestorage-test.cpp b/tests/unit/unittest/imagecachestorage-test.cpp index 8b33373b32c..285177c4276 100644 --- a/tests/unit/unittest/imagecachestorage-test.cpp +++ b/tests/unit/unittest/imagecachestorage-test.cpp @@ -57,29 +57,6 @@ protected: QIcon icon1{QPixmap::fromImage(image1)}; }; -TEST_F(ImageCacheStorageTest, Initialize) -{ - InSequence s; - - EXPECT_CALL(databaseMock, exclusiveBegin()); - EXPECT_CALL(databaseMock, - execute(Eq("CREATE TABLE IF NOT EXISTS images(id INTEGER PRIMARY KEY, name TEXT " - "NOT NULL UNIQUE, mtime INTEGER, image BLOB, smallImage BLOB)"))); - EXPECT_CALL(databaseMock, - execute(Eq("CREATE TABLE IF NOT EXISTS icons(id INTEGER PRIMARY KEY, name TEXT " - "NOT NULL UNIQUE, mtime INTEGER, icon BLOB)"))); - EXPECT_CALL(databaseMock, commit()); - EXPECT_CALL(databaseMock, immediateBegin()); - EXPECT_CALL(databaseMock, prepare(Eq(selectImageStatement.sqlStatement))); - EXPECT_CALL(databaseMock, prepare(Eq(selectSmallImageStatement.sqlStatement))); - EXPECT_CALL(databaseMock, prepare(Eq(selectIconStatement.sqlStatement))); - EXPECT_CALL(databaseMock, prepare(Eq(upsertImageStatement.sqlStatement))); - EXPECT_CALL(databaseMock, prepare(Eq(upsertIconStatement.sqlStatement))); - EXPECT_CALL(databaseMock, commit()); - - QmlDesigner::ImageCacheStorage storage{databaseMock}; -} - TEST_F(ImageCacheStorageTest, FetchImageCalls) { InSequence s; @@ -449,4 +426,22 @@ TEST_F(ImageCacheStorageSlowTest, FetchNewerIcon) ASSERT_THAT(image, Optional(IsIcon(icon1))); } + +TEST_F(ImageCacheStorageSlowTest, FetchModifiedImageTime) +{ + storage.storeImage("/path/to/component", {123}, image1, smallImage1); + + auto timeStamp = storage.fetchModifiedImageTime("/path/to/component"); + + ASSERT_THAT(timeStamp, Eq(Sqlite::TimeStamp{123})); +} + +TEST_F(ImageCacheStorageSlowTest, FetchInvalidModifiedImageTimeForNoEntry) +{ + storage.storeImage("/path/to/component2", {123}, image1, smallImage1); + + auto timeStamp = storage.fetchModifiedImageTime("/path/to/component"); + + ASSERT_THAT(timeStamp, Eq(Sqlite::TimeStamp{})); +} } // namespace diff --git a/tests/unit/unittest/mockimagecachestorage.h b/tests/unit/unittest/mockimagecachestorage.h index 3726672b902..50808934066 100644 --- a/tests/unit/unittest/mockimagecachestorage.h +++ b/tests/unit/unittest/mockimagecachestorage.h @@ -61,4 +61,8 @@ public: (override)); MOCK_METHOD(void, walCheckpointFull, (), (override)); + MOCK_METHOD(Sqlite::TimeStamp, + fetchModifiedImageTime, + (Utils::SmallStringView name), + (const, override)); }; diff --git a/tests/unit/unittest/sqlitereadstatementmock.h b/tests/unit/unittest/sqlitereadstatementmock.h index 225c83d5c95..29540d95daa 100644 --- a/tests/unit/unittest/sqlitereadstatementmock.h +++ b/tests/unit/unittest/sqlitereadstatementmock.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -96,6 +97,8 @@ public: valueReturnCacheSourceNameAndSourceContextId, (int) ); + MOCK_METHOD(Sqlite::TimeStamp, valueWithTransactionReturnsTimeStamp, (Utils::SmallStringView), ()); + MOCK_METHOD(QmlDesigner::SourceContextId, valueReturnsSourceContextId, (Utils::SmallStringView), ()); MOCK_METHOD(QmlDesigner::SourceContextId, valueWithTransactionReturnsSourceContextId, (int), ()); @@ -201,6 +204,8 @@ public: return valueReturnsPropertyDeclaration(queryValues...); else if constexpr (std::is_same_v) return valueWithTransactionReturnsSourceContextId(queryValues...); + else if constexpr (std::is_same_v) + return valueWithTransactionReturnsTimeStamp(queryValues...); else static_assert(!std::is_same_v, "SqliteReadStatementMock::value does not handle result type!");