File indexing completed on 2024-05-12 15:55:37

0001 // SPDX-FileCopyrightText: 2021-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0002 //
0003 // SPDX-License-Identifier: LicenseRef-KDE-Accepted-GPL
0004 
0005 #include "TestThumbnailCache.h"
0006 
0007 #include "ThumbnailCache.h"
0008 
0009 #include <kpabase/SettingsData.h>
0010 #include <kpabase/UIDelegate.h>
0011 
0012 #include <QBuffer>
0013 #include <QLoggingCategory>
0014 #include <QRegularExpression>
0015 #include <QSignalSpy>
0016 
0017 namespace
0018 {
0019 constexpr auto msgPreconditionFailed = "Precondition for test failed - please fix unit test!";
0020 constexpr auto v4IndexHexData {
0021     "00000004" // version
0022     "00000003" // current file
0023     "00033714" // offset in file
0024     "00000003" // number of thumbnails
0025     "000000160062006C00610063006B00690065002E006A00700067000000000000462C00001F0B" // "blackie.jpg"
0026     "0000001600730070006900660066005F0032002E006A0070006700000000000038A900001DFD" // "spiff_2.jpg"
0027     "0000001C006E00650077005F0077006100760065005F0032002E006A0070006700000000000000000000229D" // "new_wave_2.jpg"
0028 };
0029 constexpr auto v5IndexHexData {
0030     "00000005" // version
0031     "00000100" // v5: thumbnailsize
0032     "00000003" // current file
0033     "00033714" // offset in file
0034     "00000003" // number of thumbnails
0035     "000000160062006C00610063006B00690065002E006A00700067000000000000462C00001F0B" // "blackie.jpg"
0036     "0000001600730070006900660066005F0032002E006A0070006700000000000038A900001DFD" // "spiff_2.jpg"
0037     "0000001C006E00650077005F0077006100760065005F0032002E006A0070006700000000000000000000229D" // "new_wave_2.jpg"
0038 };
0039 }
0040 
0041 void KPATest::TestThumbnailCache::initTestCase()
0042 {
0043     // ThumbnailCache uses QHash, which is randomized by default
0044     qSetGlobalQHashSeed(0);
0045 }
0046 
0047 void KPATest::TestThumbnailCache::loadV4ThumbnailIndex()
0048 {
0049     QTemporaryDir tmpDir;
0050     QVERIFY2(tmpDir.isValid(), msgPreconditionFailed);
0051     // tmpDir.setAutoRemove(false);
0052 
0053     DB::DummyUIDelegate uiDelegate;
0054     Settings::SettingsData::setup(tmpDir.path(), uiDelegate);
0055 
0056     const QDir thumbnailDir { tmpDir.filePath(ImageManager::defaultThumbnailDirectory()) };
0057     QDir().mkdir(thumbnailDir.path());
0058     QFile thumbnailIndex { thumbnailDir.filePath(QStringLiteral("thumbnailindex")) };
0059     QVERIFY2(thumbnailIndex.open(QIODevice::WriteOnly), msgPreconditionFailed);
0060     thumbnailIndex.write(QByteArray::fromHex(v4IndexHexData));
0061     thumbnailIndex.close();
0062     QCOMPARE(thumbnailIndex.size(), 136);
0063 
0064     ImageManager::ThumbnailCache thumbnailCache { thumbnailDir.path() };
0065 
0066     QSignalSpy cacheSavedSpy { &thumbnailCache, &ImageManager::ThumbnailCache::saveComplete };
0067     QVERIFY2(cacheSavedSpy.isValid(), msgPreconditionFailed);
0068 
0069     // change this when the version changes:
0070     QCOMPARE(thumbnailCache.preferredFileVersion(), 5);
0071     // verify input as defined in v4IndexHexData:
0072     QCOMPARE(thumbnailCache.actualFileVersion(), 4);
0073     QCOMPARE(thumbnailCache.size(), 3);
0074     QVERIFY(thumbnailCache.contains(DB::FileName::fromRelativePath(QStringLiteral("blackie.jpg"))));
0075     QVERIFY(thumbnailCache.contains(DB::FileName::fromRelativePath(QStringLiteral("spiff_2.jpg"))));
0076     QVERIFY(thumbnailCache.contains(DB::FileName::fromRelativePath(QStringLiteral("new_wave_2.jpg"))));
0077     QVERIFY(!thumbnailCache.contains(DB::FileName()));
0078 
0079     // remove an empty file list to force the dirty flag:
0080     thumbnailCache.removeThumbnails(DB::FileNameList());
0081     // actually, removeThumbnails causes a save, but in case we change we want to call save explicitly:
0082     thumbnailCache.save();
0083     // save is actually called more than once, but should only be executed once:
0084     QCOMPARE(cacheSavedSpy.count(), 1);
0085 
0086     // after saving, the actual file version should have been updated
0087     QCOMPARE(thumbnailCache.actualFileVersion(), thumbnailCache.preferredFileVersion());
0088     QCOMPARE(thumbnailCache.thumbnailSize(), Settings::SettingsData::instance()->thumbnailSize());
0089 
0090     // verify data on disk:
0091     QVERIFY2(thumbnailIndex.open(QIODevice::ReadOnly), msgPreconditionFailed);
0092     const QByteArray v5Index { thumbnailIndex.readAll() };
0093     thumbnailIndex.close();
0094     QCOMPARE(v5Index, QByteArray::fromHex(v5IndexHexData));
0095 
0096     // we only have the index data - trying a lookup won't work, but shouldn't crash or something
0097     QTest::ignoreMessage(QtWarningMsg, "Failed to open thumbnail file");
0098     QTest::ignoreMessage(QtWarningMsg, "Failed to map thumbnail file");
0099     QTest::ignoreMessage(QtWarningMsg, "Failed to map thumbnail file");
0100     QVERIFY(thumbnailCache.lookup(DB::FileName::fromRelativePath(QStringLiteral("blackie.jpg"))).isNull());
0101 
0102     QSignalSpy flushedSpy { &thumbnailCache, &ImageManager::ThumbnailCache::cacheFlushed };
0103     QVERIFY2(flushedSpy.isValid(), msgPreconditionFailed);
0104     thumbnailCache.flush();
0105     QCOMPARE(flushedSpy.count(), 1);
0106     QCOMPARE(thumbnailCache.size(), 0);
0107 }
0108 
0109 void KPATest::TestThumbnailCache::insertRemove()
0110 {
0111     QTemporaryDir tmpDir;
0112     QVERIFY2(tmpDir.isValid(), msgPreconditionFailed);
0113     // tmpDir.setAutoRemove(false);
0114 
0115     DB::DummyUIDelegate uiDelegate;
0116     Settings::SettingsData::setup(tmpDir.path(), uiDelegate);
0117 
0118     const QDir thumbnailDir { tmpDir.filePath(ImageManager::defaultThumbnailDirectory()) };
0119     QDir().mkdir(thumbnailDir.path());
0120 
0121     const QRegularExpression thumbnailIndexNotFoundRegex { QStringLiteral("Thumbnail index file \"%1\" not found!")
0122                                                                .arg(thumbnailDir.filePath(QStringLiteral("thumbnailindex"))) };
0123     QTest::ignoreMessage(QtWarningMsg, thumbnailIndexNotFoundRegex);
0124     ImageManager::ThumbnailCache thumbnailCache { thumbnailDir.path() };
0125 
0126     QSignalSpy cacheSavedSpy { &thumbnailCache, &ImageManager::ThumbnailCache::saveComplete };
0127     QVERIFY2(cacheSavedSpy.isValid(), msgPreconditionFailed);
0128 
0129     thumbnailCache.save();
0130     QCOMPARE(thumbnailCache.size(), 0);
0131     // nothing stored yet - no need to save:
0132     QCOMPARE(cacheSavedSpy.count(), 0);
0133     QCOMPARE(thumbnailCache.actualFileVersion(), -1);
0134 
0135     // insert some images
0136     QSignalSpy thumbnailUpdatedSpy { &thumbnailCache, &ImageManager::ThumbnailCache::thumbnailUpdated };
0137     QVERIFY2(thumbnailUpdatedSpy.isValid(), msgPreconditionFailed);
0138     // the image needs to be valid
0139     QImage nullImage {};
0140     const auto nullImageFileName = DB::FileName::fromRelativePath(QStringLiteral("nullImage.jpg"));
0141     QTest::ignoreMessage(QtWarningMsg, "Thumbnail for file \"nullImage.jpg\" is invalid!");
0142     thumbnailCache.insert(nullImageFileName, nullImage);
0143     QCOMPARE(thumbnailCache.size(), 0);
0144     QVERIFY(!thumbnailCache.contains(nullImageFileName));
0145     QCOMPARE(thumbnailUpdatedSpy.count(), 0);
0146 
0147     const int thumbnailSize = thumbnailCache.thumbnailSize();
0148     QVERIFY2(thumbnailSize > 0, "Thumbnail size must be greater than 0!");
0149     QImage someImage { thumbnailSize + 1, thumbnailSize + 1, QImage::Format_RGB32 };
0150     someImage.fill(Qt::red);
0151     QVERIFY(!someImage.isNull());
0152     const auto someImageFileName = DB::FileName::fromRelativePath(QStringLiteral("someImage.jpg"));
0153     thumbnailCache.insert(someImageFileName, someImage);
0154     QCOMPARE(thumbnailCache.size(), 1);
0155     QVERIFY(thumbnailCache.contains(someImageFileName));
0156     QCOMPARE(thumbnailUpdatedSpy.count(), 1);
0157     thumbnailUpdatedSpy.clear();
0158 
0159     QImage otherImage { thumbnailSize, thumbnailSize, QImage::Format_RGB32 };
0160     otherImage.fill(Qt::green);
0161     QVERIFY(!otherImage.isNull());
0162     const auto otherImageFileName = DB::FileName::fromRelativePath(QStringLiteral("otherImage.jpg"));
0163     thumbnailCache.insert(otherImageFileName, otherImage);
0164     QCOMPARE(thumbnailCache.size(), 2);
0165     QVERIFY(thumbnailCache.contains(otherImageFileName));
0166     QCOMPARE(thumbnailUpdatedSpy.count(), 1);
0167     thumbnailUpdatedSpy.clear();
0168 
0169     // TODO(jzarl) inserted images should be the same as the ones we look up
0170     QByteArray someImageData;
0171     QBuffer someImageBuffer(&someImageData);
0172     bool OK = someImageBuffer.open(QIODevice::WriteOnly);
0173     QVERIFY2(OK, "someImageBuffer.open() failed!");
0174     OK = someImage.save(&someImageBuffer, "JPG");
0175     QVERIFY2(OK, "Writing someImage into buffer failed!");
0176 
0177     thumbnailCache.removeThumbnail(someImageFileName);
0178     QCOMPARE(thumbnailCache.size(), 1);
0179     QVERIFY(!thumbnailCache.contains(someImageFileName));
0180     thumbnailCache.insert(someImageFileName, someImageData);
0181     QCOMPARE(thumbnailCache.size(), 2);
0182     QVERIFY(thumbnailCache.contains(someImageFileName));
0183     const auto someImageCacheData = thumbnailCache.lookupRawData(someImageFileName);
0184     QCOMPARE(someImageCacheData, someImageData);
0185 
0186     // this should do nothing:
0187     thumbnailCache.removeThumbnails(DB::FileNameList());
0188     QCOMPARE(thumbnailCache.size(), 2);
0189     QCOMPARE(thumbnailCache.actualFileVersion(), thumbnailCache.preferredFileVersion());
0190 
0191     thumbnailCache.save();
0192     // the someImage has an incorrect size:
0193     QCOMPARE(thumbnailCache.findIncorrectlySizedThumbnails().size(), 1);
0194 
0195     // removals:
0196     QVERIFY(thumbnailCache.contains(someImageFileName));
0197     QVERIFY(thumbnailCache.contains(otherImageFileName));
0198     thumbnailCache.removeThumbnail(someImageFileName);
0199     QCOMPARE(thumbnailCache.size(), 1);
0200     QVERIFY(!thumbnailCache.contains(someImageFileName));
0201     QVERIFY(thumbnailCache.contains(otherImageFileName));
0202     thumbnailCache.save();
0203     QVERIFY(thumbnailCache.findIncorrectlySizedThumbnails().isEmpty());
0204 
0205     thumbnailCache.removeThumbnails(DB::FileNameList({ otherImageFileName }));
0206     QCOMPARE(thumbnailCache.size(), 0);
0207 
0208     // insert again and invalidate by changing thumbnail size:
0209     thumbnailCache.insert(someImageFileName, someImage);
0210     thumbnailCache.insert(otherImageFileName, otherImage);
0211     QCOMPARE(thumbnailCache.size(), 2);
0212     QSignalSpy invalidatedSpy { &thumbnailCache, &ImageManager::ThumbnailCache::cacheInvalidated };
0213     QVERIFY(invalidatedSpy.isValid());
0214     thumbnailCache.setThumbnailSize(thumbnailSize + 1);
0215     QCOMPARE(invalidatedSpy.count(), 1);
0216     QCOMPARE(thumbnailCache.size(), 0);
0217 }
0218 
0219 QTEST_MAIN(KPATest::TestThumbnailCache)
0220 
0221 // vi:expandtab:tabstop=4 shiftwidth=4:
0222 
0223 #include "moc_TestThumbnailCache.cpp"