File indexing completed on 2024-04-14 14:23:54

0001 /*
0002     This file is part of KDE
0003     SPDX-FileCopyrightText: 2006-2016 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include <KIO/FavIconRequestJob>
0009 #include <KIO/Job>
0010 #include <QDir>
0011 #include <QLoggingCategory>
0012 #include <QStandardPaths>
0013 #include <QTest>
0014 
0015 #include <QFutureSynchronizer>
0016 #include <QThreadPool>
0017 #include <QtConcurrentRun>
0018 
0019 static const char s_hostUrl[] = "http://www.google.com/index.html";
0020 static const char s_pageUrl[] = "http://www.google.com/somepage.html";
0021 static const char s_iconUrl[] = "http://www.google.com/favicon.ico";
0022 static const char s_altIconUrl[] = "http://www.ibm.com/favicon.ico";
0023 static const char s_thirdIconUrl[] = "http://www.google.fr/favicon.ico";
0024 static const char s_iconUrlForThreadTest[] = "http://www.google.de/favicon.ico";
0025 
0026 static enum NetworkAccess { Unknown, Yes, No } s_networkAccess = Unknown;
0027 static bool checkNetworkAccess()
0028 {
0029     if (s_networkAccess == Unknown) {
0030         QElapsedTimer tm;
0031         tm.start();
0032         KIO::Job *job = KIO::get(QUrl(s_iconUrl), KIO::NoReload, KIO::HideProgressInfo);
0033         if (job->exec()) {
0034             s_networkAccess = Yes;
0035             qDebug() << "Network access OK. Download time" << tm.elapsed() << "ms";
0036         } else {
0037             qWarning() << job->errorString();
0038             s_networkAccess = No;
0039         }
0040     }
0041     return s_networkAccess == Yes;
0042 }
0043 
0044 class FavIconTest : public QObject
0045 {
0046     Q_OBJECT
0047 
0048 private Q_SLOTS:
0049     void initTestCase();
0050     void favIconForUrlShouldBeEmptyInitially();
0051     void hostJobShouldDownloadIconThenUseCache();
0052     void iconUrlJobShouldDownloadIconThenUseCache();
0053     void reloadShouldReload();
0054     void failedDownloadShouldBeRemembered();
0055     void tooBigFaviconShouldAbort();
0056     void simultaneousRequestsShouldWork();
0057     void concurrentRequestsShouldWork();
0058 
0059 private:
0060 };
0061 
0062 void FavIconTest::initTestCase()
0063 {
0064     QStandardPaths::setTestModeEnabled(true);
0065 
0066     // To let ctest exit, we shouldn't start kio_http_cache_cleaner
0067     qputenv("KIO_DISABLE_CACHE_CLEANER", "yes");
0068     // To get KJob::errorString() in English
0069     qputenv("LC_ALL", "en_US.UTF-8");
0070 
0071     if (!checkNetworkAccess()) {
0072         QSKIP("no network access", SkipAll);
0073     }
0074 
0075     // Ensure we start with no cache on disk
0076     const QString favIconCacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/favicons");
0077     QDir(favIconCacheDir).removeRecursively();
0078     QVERIFY(!QFileInfo::exists(favIconCacheDir));
0079 
0080     // Enable debug output
0081     QLoggingCategory::setFilterRules(QStringLiteral("kf.kio.gui.favicons.debug=true"));
0082 }
0083 
0084 void FavIconTest::favIconForUrlShouldBeEmptyInitially()
0085 {
0086     QCOMPARE(KIO::favIconForUrl(QUrl(s_hostUrl)), QString());
0087 }
0088 
0089 // Waits for start() and checks whether a transfer job was created.
0090 static bool willDownload(KIO::FavIconRequestJob *job)
0091 {
0092     qApp->sendPostedEvents(job, QEvent::MetaCall); // start() is delayed
0093     return job->findChild<KIO::TransferJob *>();
0094 }
0095 
0096 void FavIconTest::hostJobShouldDownloadIconThenUseCache()
0097 {
0098     const QUrl url(s_hostUrl);
0099 
0100     KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url);
0101     QVERIFY(willDownload(job));
0102     QVERIFY(job->exec());
0103     const QString iconFile = job->iconFile();
0104     QVERIFY(iconFile.endsWith(QLatin1String("favicons/www.google.com.png")));
0105     QVERIFY2(QFile::exists(iconFile), qPrintable(iconFile));
0106     QVERIFY(!QIcon(iconFile).isNull()); // pass full path to QIcon
0107     // This requires https://codereview.qt-project.org/148444
0108     // QVERIFY(!QIcon::fromTheme(iconFile).isNull()); // old code ported from kdelibs4 might do that, should work too
0109 
0110     // Lookup should give the same result
0111     QCOMPARE(KIO::favIconForUrl(url), iconFile);
0112 
0113     // Second job should use the cache
0114     KIO::FavIconRequestJob *secondJob = new KIO::FavIconRequestJob(url);
0115     QVERIFY(!willDownload(secondJob));
0116     QVERIFY(secondJob->exec());
0117     QCOMPARE(secondJob->iconFile(), iconFile);
0118 
0119     // The code from the class docu
0120     QString goticonFile;
0121     {
0122         KIO::FavIconRequestJob *favJob = new KIO::FavIconRequestJob(url);
0123         connect(favJob, &KIO::FavIconRequestJob::result, this, [favJob, &goticonFile](KJob *) {
0124             if (!favJob->error()) {
0125                 goticonFile = favJob->iconFile();
0126             }
0127         });
0128         QVERIFY(favJob->exec());
0129     }
0130     QCOMPARE(goticonFile, iconFile);
0131 }
0132 
0133 void FavIconTest::iconUrlJobShouldDownloadIconThenUseCache()
0134 {
0135     const QUrl url(s_pageUrl);
0136 
0137     // Set icon URL to "ibm"
0138     KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url);
0139     job->setIconUrl(QUrl(s_altIconUrl));
0140     QVERIFY(willDownload(job));
0141     QVERIFY(job->exec());
0142     const QString iconFile = job->iconFile();
0143     QVERIFY(iconFile.endsWith(QLatin1String("favicons/www.ibm.com.png")));
0144     QVERIFY2(QFile::exists(iconFile), qPrintable(iconFile));
0145     QVERIFY(!QPixmap(iconFile).isNull()); // pass full path to QPixmap (to test this too)
0146 
0147     // Lookup should give the same result
0148     QCOMPARE(KIO::favIconForUrl(url), iconFile);
0149 
0150     // Second job should use the cache. It doesn't even need the icon url again.
0151     KIO::FavIconRequestJob *secondJob = new KIO::FavIconRequestJob(url);
0152     QVERIFY(!willDownload(secondJob));
0153     QVERIFY(secondJob->exec());
0154     QCOMPARE(secondJob->iconFile(), iconFile);
0155 
0156     // Set icon URL to "www.google.fr/favicon.ico"
0157     KIO::FavIconRequestJob *thirdJob = new KIO::FavIconRequestJob(url);
0158     thirdJob->setIconUrl(QUrl(s_thirdIconUrl));
0159     QVERIFY(willDownload(thirdJob));
0160     QVERIFY(thirdJob->exec());
0161     const QString newiconFile = thirdJob->iconFile();
0162     QVERIFY(newiconFile.endsWith(QLatin1String("favicons/www.google.fr.png")));
0163 
0164     // Lookup should give the same result
0165     QCOMPARE(KIO::favIconForUrl(url), newiconFile);
0166 }
0167 
0168 void FavIconTest::reloadShouldReload()
0169 {
0170     const QUrl url(s_hostUrl);
0171 
0172     // First job, to make sure it's in the cache (if the other tests didn't run first)
0173     KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url);
0174     QVERIFY(job->exec());
0175     const QString iconFile = job->iconFile();
0176 
0177     // Second job should use the cache
0178     KIO::FavIconRequestJob *secondJob = new KIO::FavIconRequestJob(url);
0179     QVERIFY(!willDownload(secondJob));
0180     QVERIFY(secondJob->exec());
0181     QCOMPARE(secondJob->iconFile(), iconFile);
0182 
0183     // job with Reload should not use the cache
0184     KIO::FavIconRequestJob *jobWithReload = new KIO::FavIconRequestJob(url, KIO::Reload);
0185     QVERIFY(willDownload(jobWithReload));
0186     QVERIFY(jobWithReload->exec());
0187     QCOMPARE(jobWithReload->iconFile(), iconFile);
0188 }
0189 
0190 void FavIconTest::failedDownloadShouldBeRemembered()
0191 {
0192     const QUrl url(s_pageUrl);
0193 
0194     // Set icon URL to a non-existing favicon
0195     KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url);
0196     job->setIconUrl(QUrl("https://kde.org/doesnotexist/favicon.ico"));
0197     QVERIFY(willDownload(job));
0198     QVERIFY(!job->exec());
0199     QVERIFY(job->iconFile().isEmpty());
0200     qDebug() << job->errorString();
0201     QCOMPARE(job->error(), int(KIO::ERR_DOES_NOT_EXIST));
0202     QCOMPARE(job->errorString(), QStringLiteral("The file or folder https://kde.org/doesnotexist/favicon.ico does not exist."));
0203 
0204     // Second job should use the cache and not do anything
0205     KIO::FavIconRequestJob *secondJob = new KIO::FavIconRequestJob(url);
0206     QVERIFY(!willDownload(secondJob));
0207     QVERIFY(!secondJob->exec());
0208     QVERIFY(secondJob->iconFile().isEmpty());
0209     QCOMPARE(job->error(), int(KIO::ERR_DOES_NOT_EXIST));
0210     QCOMPARE(job->errorString(), QStringLiteral("The file or folder https://kde.org/doesnotexist/favicon.ico does not exist."));
0211 }
0212 
0213 void FavIconTest::tooBigFaviconShouldAbort()
0214 {
0215     const QUrl url(s_pageUrl);
0216 
0217     // Set icon URL to a >65KB file
0218     KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url);
0219     job->setIconUrl(QUrl("http://download.kde.org/Attic/4.13.2/src/kcalc-4.13.2.tar.xz"));
0220     QVERIFY(willDownload(job));
0221     QVERIFY(!job->exec());
0222     QCOMPARE(job->error(), int(KIO::ERR_WORKER_DEFINED));
0223     QCOMPARE(job->errorString(), QStringLiteral("Icon file too big, download aborted"));
0224 }
0225 
0226 void FavIconTest::simultaneousRequestsShouldWork()
0227 {
0228     const QUrl url(s_hostUrl);
0229 
0230     // First job, to find out the iconFile and delete it
0231     QString iconFile;
0232     {
0233         KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url);
0234         QVERIFY(job->exec());
0235         iconFile = job->iconFile();
0236         QFile::remove(iconFile);
0237     }
0238 
0239     // This is a case we could maybe optimize: not downloading twice in parallel
0240     KIO::FavIconRequestJob *job1 = new KIO::FavIconRequestJob(url);
0241     job1->setAutoDelete(false);
0242     KIO::FavIconRequestJob *job2 = new KIO::FavIconRequestJob(url);
0243     job2->setAutoDelete(false);
0244     QVERIFY(willDownload(job1));
0245     QVERIFY(willDownload(job2));
0246 
0247     QVERIFY(job1->exec());
0248     QCOMPARE(job1->iconFile(), iconFile);
0249 
0250     QVERIFY(job2->exec());
0251     QCOMPARE(job2->iconFile(), iconFile);
0252 
0253     delete job1;
0254     delete job2;
0255 }
0256 
0257 static QString getAltIconUrl()
0258 {
0259     const QUrl url(s_pageUrl);
0260     // Set icon URL to one that we haven't downloaded yet
0261     KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(url);
0262     job->setIconUrl(QUrl(s_iconUrlForThreadTest));
0263     job->exec();
0264     return job->iconFile();
0265 }
0266 
0267 void FavIconTest::concurrentRequestsShouldWork()
0268 {
0269     const int numThreads = 3;
0270     QThreadPool tp;
0271     tp.setMaxThreadCount(numThreads);
0272     QVector<QFuture<QString>> futures(numThreads);
0273     for (int i = 0; i < numThreads; ++i) {
0274         futures[i] = QtConcurrent::run(&tp, getAltIconUrl);
0275     }
0276     QVERIFY(tp.waitForDone(60000));
0277 
0278     const QString firstResult = futures.at(0).result();
0279     for (int i = 1; i < numThreads; ++i) {
0280         QCOMPARE(futures.at(i).result(), firstResult);
0281     }
0282     QVERIFY(!QPixmap(firstResult).isNull());
0283 }
0284 
0285 QTEST_MAIN(FavIconTest)
0286 
0287 #include "favicontest.moc"