File indexing completed on 2024-05-12 04:19:59
0001 /* 0002 Gwenview: an image viewer 0003 Copyright 2007 Aurélien Gâteau <agateau@kde.org> 0004 0005 This program is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU General Public License 0007 as published by the Free Software Foundation; either version 2 0008 of the License, or (at your option) any later version. 0009 0010 This program is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 GNU General Public License for more details. 0014 0015 You should have received a copy of the GNU General Public License 0016 along with this program; if not, write to the Free Software 0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0018 0019 */ 0020 #include "thumbnailprovidertest.h" 0021 0022 // Qt 0023 #include <QDebug> 0024 #include <QDir> 0025 #include <QFile> 0026 #include <QImage> 0027 #include <QPainter> 0028 #include <QTest> 0029 0030 // KF 0031 #include <KIO/CopyJob> 0032 #include <KIO/DeleteJob> 0033 0034 // Local 0035 #include "../lib/thumbnailprovider/thumbnailprovider.h" 0036 #include "gwenviewconfig.h" 0037 #include "testutils.h" 0038 0039 // libc 0040 #include <cerrno> 0041 #include <cstring> 0042 0043 using namespace Gwenview; 0044 0045 QTEST_MAIN(ThumbnailProviderTest) 0046 0047 SandBox::SandBox() 0048 : mPath(QDir::currentPath() + "/sandbox") 0049 { 0050 } 0051 0052 void SandBox::initDir() 0053 { 0054 KIO::Job *job; 0055 QDir dir(mPath); 0056 if (dir.exists()) { 0057 QUrl sandBoxUrl("file://" + mPath); 0058 job = KIO::del(sandBoxUrl); 0059 QVERIFY2(job->exec(), "Couldn't delete sandbox"); 0060 } 0061 dir.mkpath("."); 0062 } 0063 0064 void SandBox::fill() 0065 { 0066 initDir(); 0067 createTestImage("red.png", 300, 200, Qt::red); 0068 createTestImage("blue.png", 200, 300, Qt::blue); 0069 createTestImage("small.png", 50, 50, Qt::green); 0070 0071 copyTestImage("orient6.jpg", 128, 256); 0072 copyTestImage("orient6-small.jpg", 32, 64); 0073 } 0074 0075 void SandBox::copyTestImage(const QString &testFileName, int width, int height) 0076 { 0077 QUrl testPath = urlForTestFile(testFileName); 0078 QUrl testDest = QUrl("file://" + mPath + '/' + testFileName); 0079 KIO::Job *job = KIO::copy(testPath, testDest); 0080 QVERIFY2(job->exec(), "Couldn't copy test image"); 0081 mSizeHash.insert(testFileName, QSize(width, height)); 0082 } 0083 0084 static QImage createColoredImage(int width, int height, const QColor &color) 0085 { 0086 QImage image(width, height, QImage::Format_RGB32); 0087 QPainter painter(&image); 0088 painter.fillRect(image.rect(), color); 0089 return image; 0090 } 0091 0092 void SandBox::createTestImage(const QString &name, int width, int height, const QColor &color) 0093 { 0094 QImage image = createColoredImage(width, height, color); 0095 image.save(mPath + '/' + name, "png"); 0096 mSizeHash.insert(name, QSize(width, height)); 0097 } 0098 0099 void ThumbnailProviderTest::initTestCase() 0100 { 0101 qRegisterMetaType<KFileItem>("KFileItem"); 0102 } 0103 0104 void ThumbnailProviderTest::init() 0105 { 0106 ThumbnailProvider::setThumbnailBaseDir(mSandBox.mPath + "/thumbnails/"); 0107 mSandBox.fill(); 0108 } 0109 0110 static void syncRun(ThumbnailProvider *provider) 0111 { 0112 QEventLoop loop; 0113 QObject::connect(provider, SIGNAL(finished()), &loop, SLOT(quit())); 0114 loop.exec(); 0115 } 0116 0117 void ThumbnailProviderTest::testLoadLocal() 0118 { 0119 QDir dir(mSandBox.mPath); 0120 0121 // Create a list of items which will be thumbnailed 0122 KFileItemList list; 0123 const auto entryInfoList = dir.entryInfoList(QDir::Files); 0124 for (const QFileInfo &info : entryInfoList) { 0125 QUrl url("file://" + info.absoluteFilePath()); 0126 KFileItem item(url); 0127 list << item; 0128 } 0129 0130 // Generate the thumbnails 0131 ThumbnailProvider provider; 0132 provider.setThumbnailGroup(ThumbnailGroup::Normal); 0133 provider.appendItems(list); 0134 QSignalSpy spy(&provider, SIGNAL(thumbnailLoaded(KFileItem, QPixmap, QSize, qulonglong))); 0135 syncRun(&provider); 0136 while (!ThumbnailProvider::isThumbnailWriterEmpty()) { 0137 QTest::qWait(100); 0138 } 0139 0140 // Check we generated the correct number of thumbnails 0141 QDir thumbnailDir = ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Normal); 0142 // There should be one file less because small.png is a png and is too 0143 // small to have a thumbnail 0144 const QStringList entryList = thumbnailDir.entryList(QStringList("*.png")); 0145 QCOMPARE(entryList.count(), mSandBox.mSizeHash.size() - 1); 0146 0147 // Check thumbnail keys 0148 QHash<KFileItem, QHash<QString, QString>> thumbnailHash; 0149 for (const QString &name : entryList) { 0150 QImage thumb; 0151 QVERIFY(thumb.load(thumbnailDir.filePath(name))); 0152 0153 QUrl url(thumb.text("Thumb::URI")); 0154 KFileItem item = list.findByUrl(url); 0155 QVERIFY(!item.isNull()); 0156 0157 QSize originalSize = mSandBox.mSizeHash.value(item.url().fileName()); 0158 uint mtime = item.time(KFileItem::ModificationTime).toSecsSinceEpoch(); 0159 0160 if (mtime == uint(-1)) { 0161 // This happens from time to time on build.kde.org, but I haven't 0162 // been able to reproduce it locally, so let's try to gather more 0163 // information. 0164 qWarning() << "mtime == -1 for url" << url << ". This should not happen!"; 0165 qWarning() << "errno:" << errno << "message:" << strerror(errno); 0166 qWarning() << "QFile::exists(" << url.toLocalFile() << "):" << QFile::exists(url.toLocalFile()); 0167 qWarning() << "Recalculating mtime" << item.time(KFileItem::ModificationTime).toSecsSinceEpoch(); 0168 QFAIL("Invalid time for test KFileItem"); 0169 } 0170 0171 QCOMPARE(thumb.text("Thumb::Image::Width"), QString::number(originalSize.width())); 0172 QCOMPARE(thumb.text("Thumb::Image::Height"), QString::number(originalSize.height())); 0173 QCOMPARE(thumb.text("Thumb::Mimetype"), item.mimetype()); 0174 QCOMPARE(thumb.text("Thumb::Size"), QString::number(item.size())); 0175 QCOMPARE(thumb.text("Thumb::MTime"), QString::number(mtime)); 0176 } 0177 0178 // Check what was in the thumbnailLoaded() signals 0179 QCOMPARE(spy.count(), mSandBox.mSizeHash.size()); 0180 QSignalSpy::ConstIterator it = spy.constBegin(), end = spy.constEnd(); 0181 for (; it != end; ++it) { 0182 const QVariantList args = *it; 0183 const KFileItem item = qvariant_cast<KFileItem>(args.at(0)); 0184 const QSize size = args.at(2).toSize(); 0185 const QSize expectedSize = mSandBox.mSizeHash.value(item.url().fileName()); 0186 QCOMPARE(size, expectedSize); 0187 } 0188 } 0189 0190 void ThumbnailProviderTest::testUseEmbeddedOrNot() 0191 { 0192 QImage expectedThumbnail; 0193 QPixmap thumbnailPix; 0194 SandBox sandBox; 0195 sandBox.initDir(); 0196 // This image is red (0xfe0000) and 256x128 but contains a white 128x64 thumbnail 0197 sandBox.copyTestImage("embedded-thumbnail.jpg", 256, 128); 0198 0199 KFileItemList list; 0200 QUrl url("file://" + QDir(sandBox.mPath).absoluteFilePath("embedded-thumbnail.jpg")); 0201 list << KFileItem(url); 0202 0203 // Loading a normal thumbnail should bring the white one 0204 { 0205 ThumbnailProvider provider; 0206 provider.setThumbnailGroup(ThumbnailGroup::Normal); 0207 provider.appendItems(list); 0208 QSignalSpy spy(&provider, SIGNAL(thumbnailLoaded(KFileItem, QPixmap, QSize, qulonglong))); 0209 syncRun(&provider); 0210 0211 QCOMPARE(spy.count(), 1); 0212 expectedThumbnail = createColoredImage(128, 64, Qt::white); 0213 thumbnailPix = qvariant_cast<QPixmap>(spy.at(0).at(1)); 0214 QVERIFY(TestUtils::imageCompare(expectedThumbnail, thumbnailPix.toImage())); 0215 } 0216 0217 // Loading a large thumbnail should bring the red one, unless thumbnails are deleted on exit, 0218 // which should bring the white one 0219 { 0220 ThumbnailProvider provider; 0221 provider.setThumbnailGroup(ThumbnailGroup::Large); 0222 provider.appendItems(list); 0223 QSignalSpy spy(&provider, SIGNAL(thumbnailLoaded(KFileItem, QPixmap, QSize, qulonglong))); 0224 syncRun(&provider); 0225 0226 QCOMPARE(spy.count(), 1); 0227 if (GwenviewConfig::lowResourceUsageMode()) { 0228 expectedThumbnail = createColoredImage(128, 64, Qt::white); 0229 } else { 0230 expectedThumbnail = createColoredImage(256, 128, QColor(254, 0, 0)); 0231 } 0232 thumbnailPix = qvariant_cast<QPixmap>(spy.at(0).at(1)); 0233 QVERIFY(TestUtils::imageCompare(expectedThumbnail, thumbnailPix.toImage())); 0234 } 0235 } 0236 0237 void ThumbnailProviderTest::testLoadRemote() 0238 { 0239 QUrl url = setUpRemoteTestDir("test.png"); 0240 if (!url.isValid()) { 0241 QSKIP("Not running this test: failed to setup remote test dir."); 0242 } 0243 url = url.adjusted(QUrl::StripTrailingSlash); 0244 url.setPath(url.path() + '/' + "test.png"); 0245 0246 KFileItemList list; 0247 KFileItem item(url); 0248 list << item; 0249 0250 ThumbnailProvider provider; 0251 provider.setThumbnailGroup(ThumbnailGroup::Normal); 0252 provider.appendItems(list); 0253 syncRun(&provider); 0254 while (!ThumbnailProvider::isThumbnailWriterEmpty()) { 0255 QTest::qWait(100); 0256 } 0257 0258 QDir thumbnailDir = ThumbnailProvider::thumbnailBaseDir(ThumbnailGroup::Normal); 0259 QStringList entryList = thumbnailDir.entryList(QStringList("*.png")); 0260 QCOMPARE(entryList.count(), 1); 0261 } 0262 0263 void ThumbnailProviderTest::testRemoveItemsWhileGenerating() 0264 { 0265 QDir dir(mSandBox.mPath); 0266 0267 // Create a list of items which will be thumbnailed 0268 KFileItemList list; 0269 const auto entryInfoList = dir.entryInfoList(QDir::Files); 0270 for (const QFileInfo &info : entryInfoList) { 0271 QUrl url("file://" + info.absoluteFilePath()); 0272 KFileItem item(url); 0273 list << item; 0274 } 0275 0276 // Start generating thumbnails for items 0277 ThumbnailProvider provider; 0278 provider.setThumbnailGroup(ThumbnailGroup::Normal); 0279 provider.appendItems(list); 0280 QEventLoop loop; 0281 connect(&provider, SIGNAL(finished()), &loop, SLOT(quit())); 0282 0283 // Remove items, it should not crash 0284 provider.removeItems(list); 0285 loop.exec(); 0286 } 0287 0288 #include "moc_thumbnailprovidertest.cpp"