Warning, file /frameworks/kio/autotests/kdirlistertest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kdirlistertest.h"
0009 
0010 #include "jobuidelegatefactory.h"
0011 #include "kiotesthelper.h"
0012 #include <kio/copyjob.h>
0013 #include <kio/deletejob.h>
0014 #include <kio/jobuidelegateextension.h>
0015 #include <kio/simplejob.h>
0016 #include <kprotocolinfo.h>
0017 
0018 #include <KDirWatch>
0019 
0020 #include <QDebug>
0021 #include <QTemporaryFile>
0022 #include <QTest>
0023 
0024 QTEST_MAIN(KDirListerTest)
0025 
0026 static constexpr bool s_workaroundBrokenInotify = false;
0027 
0028 GlobalInits::GlobalInits()
0029 {
0030     // Must be done before the QSignalSpys connect
0031     qRegisterMetaType<KFileItem>();
0032     qRegisterMetaType<KFileItemList>();
0033     qRegisterMetaType<KIO::Job *>();
0034 }
0035 
0036 QString KDirListerTest::tempPath() const
0037 {
0038     return m_tempDir->path() + '/';
0039 }
0040 
0041 void KDirListerTest::initTestCase()
0042 {
0043     // To avoid failing on broken locally defined MIME types
0044     QStandardPaths::setTestModeEnabled(true);
0045 
0046     m_tempDir.reset(new QTemporaryDir(homeTmpDir()));
0047 
0048     // No message dialogs
0049     KIO::setDefaultJobUiDelegateExtension(nullptr);
0050     KIO::setDefaultJobUiDelegateFactoryV2(nullptr);
0051 
0052     m_exitCount = 1;
0053 
0054     s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-120); // 2 minutes ago
0055 
0056     // Create test data:
0057     /*
0058      * PATH/toplevelfile_1
0059      * PATH/toplevelfile_2
0060      * PATH/toplevelfile_3
0061      * PATH/subdir
0062      * PATH/subdir/testfile
0063      * PATH/subdir/subsubdir
0064      * PATH/subdir/subsubdir/testfile
0065      */
0066     createTestFile(tempPath() + "toplevelfile_1");
0067     createTestFile(tempPath() + "toplevelfile_2");
0068     createTestFile(tempPath() + "toplevelfile_3");
0069     createTestDirectory(tempPath() + "subdir");
0070     createTestDirectory(tempPath() + "subdir/subsubdir");
0071 
0072     qRegisterMetaType<QList<QPair<KFileItem, KFileItem>>>();
0073 }
0074 
0075 void KDirListerTest::cleanup()
0076 {
0077     m_dirLister.clearSpies();
0078     disconnect(&m_dirLister, nullptr, this, nullptr);
0079 }
0080 
0081 void KDirListerTest::testInvalidUrl()
0082 {
0083     m_dirLister.openUrl(QUrl(":/"), KDirLister::NoFlags);
0084     QCOMPARE(m_dirLister.spyStarted.count(), 1);
0085     QVERIFY(m_dirLister.spyJobError.wait());
0086     QCOMPARE(m_dirLister.spyJobError.at(0).at(0).value<KIO::Job *>()->error(), KIO::ERR_MALFORMED_URL);
0087     QCOMPARE(m_dirLister.spyCompleted.count(), 0);
0088     QVERIFY(m_dirLister.isFinished());
0089 }
0090 
0091 void KDirListerTest::testNonListableUrl()
0092 {
0093     m_dirLister.openUrl(QUrl("data:foo"), KDirLister::NoFlags);
0094     QCOMPARE(m_dirLister.spyStarted.count(), 1);
0095     QVERIFY(m_dirLister.spyJobError.wait());
0096     QCOMPARE(m_dirLister.spyJobError.at(0).at(0).value<KIO::Job *>()->error(), KIO::ERR_UNSUPPORTED_ACTION);
0097     QCOMPARE(m_dirLister.spyCompleted.count(), 0);
0098     QVERIFY(m_dirLister.isFinished());
0099 }
0100 
0101 void KDirListerTest::testOpenUrl()
0102 {
0103     m_items.clear();
0104     const QString path = tempPath();
0105     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0106     // The call to openUrl itself, emits started
0107     m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
0108 
0109     QCOMPARE(m_dirLister.spyStarted.count(), 1);
0110     QCOMPARE(m_dirLister.spyCompleted.count(), 0);
0111     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0);
0112     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0113     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0114     QCOMPARE(m_dirLister.spyClear.count(), 1);
0115 
0116 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0117     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0118 #endif
0119     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0120 
0121     QCOMPARE(m_dirLister.spyRedirection.count(), 0);
0122     QCOMPARE(m_items.count(), 0);
0123     QVERIFY(!m_dirLister.isFinished());
0124 
0125     // then wait for completed
0126     qDebug("waiting for completed");
0127     QTRY_COMPARE(m_dirLister.spyStarted.count(), 1);
0128     QTRY_COMPARE(m_dirLister.spyCompleted.count(), 1);
0129     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1);
0130     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0131     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0132     QCOMPARE(m_dirLister.spyClear.count(), 1);
0133 
0134 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0135     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0136 #endif
0137     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0138 
0139     QCOMPARE(m_dirLister.spyRedirection.count(), 0);
0140     // qDebug() << m_items;
0141     // qDebug() << "In dir" << QDir(path).entryList( QDir::AllEntries | QDir::NoDotAndDotDot);
0142     QCOMPARE(m_items.count(), fileCount());
0143     QVERIFY(m_dirLister.isFinished());
0144     disconnect(&m_dirLister, nullptr, this, nullptr);
0145 
0146     const QString fileName = QStringLiteral("toplevelfile_3");
0147     const QUrl itemUrl = QUrl::fromLocalFile(path + fileName);
0148     KFileItem byName = m_dirLister.findByName(fileName);
0149     QVERIFY(!byName.isNull());
0150     QCOMPARE(byName.url().toString(), itemUrl.toString());
0151     QCOMPARE(byName.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName);
0152     KFileItem byUrl = m_dirLister.findByUrl(itemUrl);
0153     QVERIFY(!byUrl.isNull());
0154     QCOMPARE(byUrl.url().toString(), itemUrl.toString());
0155     QCOMPARE(byUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName);
0156     KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl);
0157     QVERIFY(!itemForUrl.isNull());
0158     QCOMPARE(itemForUrl.url().toString(), itemUrl.toString());
0159     QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName);
0160 
0161     KFileItem rootByUrl = m_dirLister.findByUrl(QUrl::fromLocalFile(path));
0162     QVERIFY(!rootByUrl.isNull());
0163     QCOMPARE(QString(rootByUrl.url().toLocalFile() + '/'), path);
0164 
0165     m_dirLister.clearSpies(); // for the tests that call testOpenUrl for setup
0166 }
0167 
0168 // This test assumes testOpenUrl was run before. So m_dirLister is holding the items already.
0169 void KDirListerTest::testOpenUrlFromCache()
0170 {
0171     // Do the same again, it should behave the same, even with the items in the cache
0172     testOpenUrl();
0173 
0174     // Get into the case where another dirlister is holding the items
0175     {
0176         m_items.clear();
0177         const QString path = tempPath();
0178         MyDirLister secondDirLister;
0179         connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0180 
0181         secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
0182         QCOMPARE(secondDirLister.spyStarted.count(), 1);
0183         QCOMPARE(secondDirLister.spyCompleted.count(), 0);
0184         QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 0);
0185         QCOMPARE(secondDirLister.spyCanceled.count(), 0);
0186         QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0);
0187         QCOMPARE(secondDirLister.spyClear.count(), 1);
0188 
0189 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0190         QCOMPARE(secondDirLister.spyClearQUrl.count(), 0);
0191 #endif
0192         QCOMPARE(secondDirLister.spyClearDir.count(), 0);
0193 
0194         QCOMPARE(m_items.count(), 0);
0195         QVERIFY(!secondDirLister.isFinished());
0196 
0197         // then wait for completed
0198         qDebug("waiting for completed");
0199         QTRY_COMPARE(secondDirLister.spyStarted.count(), 1);
0200         QTRY_COMPARE(secondDirLister.spyCompleted.count(), 1);
0201         QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 1);
0202         QCOMPARE(secondDirLister.spyCanceled.count(), 0);
0203         QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0);
0204         QCOMPARE(secondDirLister.spyClear.count(), 1);
0205 
0206 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0207         QCOMPARE(secondDirLister.spyClearQUrl.count(), 0);
0208 #endif
0209         QCOMPARE(secondDirLister.spyClearDir.count(), 0);
0210 
0211         QCOMPARE(m_items.count(), 4);
0212         QVERIFY(secondDirLister.isFinished());
0213     }
0214 
0215     disconnect(&m_dirLister, nullptr, this, nullptr);
0216 }
0217 
0218 // This test assumes testOpenUrl was run before. So m_dirLister is holding the items already.
0219 // This test creates 1 file in the temporary directory
0220 void KDirListerTest::testNewItem()
0221 {
0222     QCOMPARE(m_items.count(), 4);
0223     const QString path = tempPath();
0224     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0225 
0226     qDebug() << "Creating a new file";
0227     const QString fileName = QStringLiteral("toplevelfile_new");
0228 
0229     createSimpleFile(path + fileName);
0230 
0231     QTRY_COMPARE(m_items.count(), 5);
0232     QCOMPARE(m_dirLister.spyStarted.count(), 1); // Updates call started
0233     QCOMPARE(m_dirLister.spyCompleted.count(), 1); // and completed
0234     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1);
0235     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0236     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0237     QCOMPARE(m_dirLister.spyClear.count(), 0);
0238 
0239 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0240     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0241 #endif
0242     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0243 
0244     const QUrl itemUrl = QUrl::fromLocalFile(path + fileName);
0245     KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl);
0246     QVERIFY(!itemForUrl.isNull());
0247     QCOMPARE(itemForUrl.url().toString(), itemUrl.toString());
0248     QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName);
0249     disconnect(&m_dirLister, nullptr, this, nullptr);
0250 }
0251 
0252 // This test assumes testNewItem was run before. So m_dirLister is holding the items already.
0253 // This test creates 100 more files in the temporary directory in reverse order
0254 void KDirListerTest::testNewItems()
0255 {
0256     QCOMPARE(m_items.count(), 5);
0257     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0258 
0259     const QString path = tempPath();
0260 
0261     qDebug() << "Creating 100 new files";
0262     for (int i = 50; i > 0; i--) {
0263         createSimpleFile(path + QString("toplevelfile_new_%1").arg(i));
0264     }
0265     QTest::qWait(1000); // Create them with 1s difference
0266     for (int i = 100; i > 50; i--) {
0267         createSimpleFile(path + QString("toplevelfile_new_%1").arg(i));
0268     }
0269 
0270     // choose one of the new created files
0271     const QString fileName = QStringLiteral("toplevelfile_new_50");
0272 
0273     QTRY_COMPARE(m_items.count(), 105);
0274 
0275     QVERIFY(m_dirLister.spyStarted.count() > 0 && m_dirLister.spyStarted.count() < 3); // Updates call started, probably twice
0276     QVERIFY(m_dirLister.spyCompleted.count() > 0 && m_dirLister.spyCompleted.count() < 3); // and completed, probably twice
0277     QVERIFY(m_dirLister.spyCompletedQUrl.count() < 3);
0278     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0279     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0280     QCOMPARE(m_dirLister.spyClear.count(), 0);
0281 
0282 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0283     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0284 #endif
0285     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0286 
0287     const QUrl itemUrl = QUrl::fromLocalFile(path + fileName);
0288     KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl);
0289     QVERIFY(!itemForUrl.isNull());
0290     QCOMPARE(itemForUrl.url().toString(), itemUrl.toString());
0291     QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName);
0292 }
0293 
0294 void KDirListerTest::benchFindByUrl()
0295 {
0296     // The time used should be in the order of O(100*log2(100))
0297     const QString path = tempPath();
0298     QBENCHMARK {
0299         for (int i = 100; i > 0; i--) {
0300             KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path + QString("toplevelfile_new_%1").arg(i)));
0301             QVERIFY(!cachedItem.isNull());
0302         }
0303     }
0304 }
0305 
0306 void KDirListerTest::testNewItemByCopy()
0307 {
0308     // This test creates a file using KIO::copyAs, like knewmenu.cpp does.
0309     // Useful for testing #192185, i.e. whether we catch the kdirwatch event and avoid
0310     // a KFileItem::refresh().
0311     const int origItemCount = m_items.count();
0312     const QString path = tempPath();
0313     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0314 
0315     QTest::qWait(1000); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything.
0316 
0317     const QString fileName = QStringLiteral("toplevelfile_copy");
0318     const QUrl itemUrl = QUrl::fromLocalFile(path + fileName);
0319     KIO::CopyJob *job = KIO::copyAs(QUrl::fromLocalFile(path + "toplevelfile_3"), itemUrl, KIO::HideProgressInfo);
0320     job->exec();
0321 
0322     // Give time for KDirWatch/KDirNotify to notify us
0323     QTRY_COMPARE(m_items.count(), origItemCount + 1);
0324 
0325     QCOMPARE(m_dirLister.spyStarted.count(), 1); // Updates call started
0326     QCOMPARE(m_dirLister.spyCompleted.count(), 1); // and completed
0327     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1);
0328     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0329     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0330     QCOMPARE(m_dirLister.spyClear.count(), 0);
0331 
0332 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0333     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0334 #endif
0335     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0336 
0337     // Give some time to KDirWatch
0338     QTest::qWait(1000);
0339 
0340     KFileItem itemForUrl = KDirLister::cachedItemForUrl(itemUrl);
0341     QVERIFY(!itemForUrl.isNull());
0342     QCOMPARE(itemForUrl.url().toString(), itemUrl.toString());
0343     QCOMPARE(itemForUrl.entry().stringValue(KIO::UDSEntry::UDS_NAME), fileName);
0344 }
0345 
0346 void KDirListerTest::testNewItemsInSymlink() // #213799
0347 {
0348     const int origItemCount = m_items.count();
0349     QCOMPARE(fileCount(), origItemCount);
0350     const QString path = tempPath();
0351     QTemporaryFile tempFile;
0352     QVERIFY(tempFile.open());
0353     const QString symPath = tempFile.fileName() + "_link";
0354     tempFile.close();
0355     const bool symlinkOk = KIOPrivate::createSymlink(path, symPath);
0356     if (!symlinkOk) {
0357         const QString error =
0358             QString::fromLatin1("Failed to create symlink '%1' pointing to '%2': %3").arg(symPath, path, QString::fromLocal8Bit(strerror(errno)));
0359         QVERIFY2(symlinkOk, qPrintable(error));
0360     }
0361     MyDirLister dirLister2;
0362     m_items2.clear();
0363     m_items.clear();
0364     connect(&dirLister2, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2);
0365     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0366 
0367     // The initial listing
0368     m_dirLister.openUrl(QUrl::fromLocalFile(path));
0369     dirLister2.openUrl(QUrl::fromLocalFile(symPath));
0370     QTRY_COMPARE(m_items.count(), m_items2.count());
0371     QTRY_COMPARE(m_items.count(), origItemCount);
0372     QTRY_VERIFY(dirLister2.isFinished());
0373 
0374     QTest::qWait(100); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything.
0375 
0376     qDebug() << "Creating new file";
0377     const QString fileName = QStringLiteral("toplevelfile_newinlink");
0378     createSimpleFile(path + fileName);
0379 
0380     if (s_workaroundBrokenInotify) {
0381         org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(path));
0382     }
0383 
0384     // Give time for KDirWatch to notify us
0385     QTRY_COMPARE(m_items2.count(), origItemCount + 1);
0386     QTRY_COMPARE(m_items.count(), origItemCount + 1);
0387 
0388     // Now create an item using the symlink-path
0389     const QString fileName2 = QStringLiteral("toplevelfile_newinlink2");
0390     {
0391         createSimpleFile(path + fileName2);
0392 
0393         // Give time for KDirWatch to notify us
0394         QTRY_COMPARE(m_items2.count(), origItemCount + 2);
0395         QTRY_COMPARE(m_items.count(), origItemCount + 2);
0396     }
0397     QCOMPARE(fileCount(), m_items.count());
0398 
0399     // Test file deletion
0400     {
0401         qDebug() << "Deleting" << (path + fileName);
0402         QTest::qWait(100); // for timestamp difference
0403         QFile::remove(path + fileName);
0404         QTRY_COMPARE(dirLister2.spyItemsDeleted.count(), 1);
0405         QTRY_COMPARE(m_dirLister.spyItemsDeleted.count(), 1);
0406         const KFileItem item = dirLister2.spyItemsDeleted[0][0].value<KFileItemList>().at(0);
0407         QCOMPARE(item.url().toLocalFile(), QString(symPath + '/' + fileName));
0408     }
0409     // Test file deletion in symlink dir #469254
0410     {
0411         qDebug() << "Deleting" << (symPath + "/" + fileName2);
0412         QTest::qWait(100); // for timestamp difference
0413         QFile::remove(symPath + "/" + fileName2);
0414         QTRY_COMPARE(dirLister2.spyItemsDeleted.count(), 2);
0415         QTRY_COMPARE(m_dirLister.spyItemsDeleted.count(), 2);
0416         const KFileItem item = dirLister2.spyItemsDeleted[1][0].value<KFileItemList>().at(0);
0417         QCOMPARE(item.url().toLocalFile(), QString(symPath + '/' + fileName2));
0418     }
0419 
0420     // TODO: test file update.
0421     disconnect(&m_dirLister, nullptr, this, nullptr);
0422 
0423     QFile::remove(symPath);
0424     m_dirLister.openUrl(QUrl::fromLocalFile(tempPath()));
0425 }
0426 
0427 // This test assumes testOpenUrl was run before. So m_dirLister is holding the items already.
0428 // Modifies one of the files to have html content
0429 void KDirListerTest::testRefreshItems()
0430 {
0431     m_refreshedItems.clear();
0432 
0433     const QString path = tempPath();
0434     const QString fileName = path + "toplevelfile_1";
0435     KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(fileName));
0436     QVERIFY(!cachedItem.isNull());
0437     QCOMPARE(cachedItem.mimetype(), QString("application/octet-stream"));
0438 
0439     connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems);
0440 
0441     QFile file(fileName);
0442     QVERIFY(file.open(QIODevice::Append));
0443     file.write(QByteArray("<html>"));
0444     file.close();
0445     QCOMPARE(QFileInfo(fileName).size(), 11LL /*Hello world*/ + 6 /*<html>*/);
0446 
0447     QTRY_VERIFY(!m_refreshedItems.isEmpty());
0448 
0449     QCOMPARE(m_dirLister.spyStarted.count(), 0); // fast path: no directory listing needed
0450     QVERIFY(m_dirLister.spyCompleted.count() < 2);
0451     QVERIFY(m_dirLister.spyCompletedQUrl.count() < 2);
0452     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0453     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0454     QCOMPARE(m_dirLister.spyClear.count(), 0);
0455 
0456 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0457     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0458 #endif
0459     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0460 
0461     QCOMPARE(m_refreshedItems.count(), 1);
0462     QPair<KFileItem, KFileItem> entry = m_refreshedItems.first();
0463     QCOMPARE(entry.first.url().toLocalFile(), fileName);
0464     QCOMPARE(entry.first.size(), KIO::filesize_t(11));
0465     QCOMPARE(entry.first.mimetype(), QString("application/octet-stream"));
0466     QCOMPARE(entry.second.url().toLocalFile(), fileName);
0467     QCOMPARE(entry.second.size(), KIO::filesize_t(11 /*Hello world*/ + 6 /*<html>*/));
0468     QCOMPARE(entry.second.mimetype(), QString("text/html"));
0469 
0470     // Let's see what KDirLister has in cache now
0471     cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(fileName));
0472     QCOMPARE(cachedItem.size(), KIO::filesize_t(11 /*Hello world*/ + 6 /*<html>*/));
0473     m_refreshedItems.clear();
0474 }
0475 
0476 // Refresh the root item, plus a hidden file, e.g. changing its icon. #190535
0477 void KDirListerTest::testRefreshRootItem()
0478 {
0479     // This test assumes testOpenUrl was run before. So m_dirLister is holding the items already.
0480     m_refreshedItems.clear();
0481     m_refreshedItems2.clear();
0482 
0483     // The item will be the root item of dirLister2, but also a child item
0484     // of m_dirLister.
0485     // In #190535 it would show "." instead of the subdir name, after a refresh...
0486     const QString path = tempPath() + "subdir";
0487     MyDirLister dirLister2;
0488     fillDirLister2(dirLister2, path);
0489 
0490     // Change the subdir by creating a file in it
0491     waitUntilMTimeChange(path);
0492     const QString foobar = path + "/.foobar";
0493     createSimpleFile(foobar);
0494 
0495     connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems);
0496 
0497     // Arguably, the mtime change of "subdir" should lead to a refreshItem of subdir in the root dir.
0498     // So the next line shouldn't be necessary, if KDirLister did this correctly. This isn't what this test is about though.
0499     org::kde::KDirNotify::emitFilesChanged(QList<QUrl>{QUrl::fromLocalFile(path)});
0500     QTRY_VERIFY(!m_refreshedItems.isEmpty());
0501 
0502     QCOMPARE(m_dirLister.spyStarted.count(), 0);
0503     QCOMPARE(m_dirLister.spyCompleted.count(), 0);
0504     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0);
0505     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0506     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0507     QCOMPARE(m_dirLister.spyClear.count(), 0);
0508 
0509 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0510     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0511 #endif
0512     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0513 
0514     QCOMPARE(m_refreshedItems.count(), 1);
0515     QPair<KFileItem, KFileItem> entry = m_refreshedItems.first();
0516     QCOMPARE(entry.first.url().toLocalFile(), path);
0517     QCOMPARE(entry.first.name(), QString("subdir"));
0518     QCOMPARE(entry.second.url().toLocalFile(), path);
0519     QCOMPARE(entry.second.name(), QString("subdir"));
0520 
0521     QCOMPARE(m_refreshedItems2.count(), 1);
0522     entry = m_refreshedItems2.first();
0523     QCOMPARE(entry.first.url().toLocalFile(), path);
0524     QCOMPARE(entry.second.url().toLocalFile(), path);
0525     // item name() doesn't matter here, it's the root item.
0526 
0527     m_refreshedItems.clear();
0528     m_refreshedItems2.clear();
0529 
0530     waitUntilMTimeChange(path);
0531     const QString directoryFile = path + "/.directory";
0532     createSimpleFile(directoryFile);
0533 
0534     org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(path));
0535     QTest::qWait(200);
0536     // The order of these two is not deterministic
0537     org::kde::KDirNotify::emitFilesChanged(QList<QUrl>{QUrl::fromLocalFile(directoryFile)});
0538     org::kde::KDirNotify::emitFilesChanged(QList<QUrl>{QUrl::fromLocalFile(path)});
0539     QTRY_VERIFY(!m_refreshedItems.isEmpty());
0540 
0541     QCOMPARE(m_refreshedItems.count(), 1);
0542     entry = m_refreshedItems.first();
0543     QCOMPARE(entry.first.url().toLocalFile(), path);
0544     QCOMPARE(entry.second.url().toLocalFile(), path);
0545 
0546     m_refreshedItems.clear();
0547     m_refreshedItems2.clear();
0548 
0549     // Note: this test leaves the .directory file as a side effect.
0550     // Hidden though, shouldn't matter.
0551 }
0552 
0553 void KDirListerTest::testDeleteItem()
0554 {
0555     testOpenUrl(); // ensure m_items is up-to-date
0556 
0557     const int origItemCount = m_items.count();
0558     QCOMPARE(fileCount(), origItemCount);
0559     const QString path = tempPath();
0560 
0561     // qDebug() << "Removing " << path+"toplevelfile_new";
0562     QFile::remove(path + QString("toplevelfile_new"));
0563     // the remove() doesn't always trigger kdirwatch in stat mode, if this all happens in the same second
0564     KDirWatch::self()->setDirty(path);
0565 
0566     // The signal should be emitted once with the deleted file
0567     QTRY_COMPARE(m_dirLister.spyItemsDeleted.count(), 1);
0568 
0569     // OK now kdirlister told us the file was deleted, let's try a re-listing
0570     m_items.clear();
0571     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0572     m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
0573     QVERIFY(!m_dirLister.isFinished());
0574 
0575     QTRY_COMPARE(m_items.count(), origItemCount - 1);
0576     QVERIFY(m_dirLister.isFinished());
0577 
0578     disconnect(&m_dirLister, nullptr, this, nullptr);
0579     QCOMPARE(fileCount(), m_items.count());
0580 }
0581 
0582 void KDirListerTest::testDeleteItems()
0583 {
0584     testOpenUrl(); // ensure m_items is up-to-date
0585 
0586     const int origItemCount = m_items.count();
0587     QCOMPARE(fileCount(), origItemCount);
0588     const QString path = tempPath();
0589 
0590     qDebug() << "Removing 100 files from " << path;
0591     for (int i = 0; i <= 100; ++i) {
0592         QFile::remove(path + QString("toplevelfile_new_%1").arg(i));
0593     }
0594     // the remove() doesn't always trigger kdirwatch in stat mode, if this all happens in the same second
0595     KDirWatch::self()->setDirty(path);
0596 
0597     // The signal could be emitted 1 time with all the deleted files or more times
0598     QTRY_VERIFY(m_dirLister.spyItemsDeleted.count() > 0);
0599 
0600     // OK now kdirlister told us the file was deleted, let's try a re-listing
0601     m_items.clear();
0602     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0603     m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
0604     QTRY_COMPARE(m_items.count(), origItemCount - 100);
0605     QVERIFY(m_dirLister.isFinished());
0606 
0607     disconnect(&m_dirLister, nullptr, this, nullptr);
0608     QCOMPARE(fileCount(), m_items.count());
0609 }
0610 
0611 void KDirListerTest::testRenameItem()
0612 {
0613     m_refreshedItems2.clear();
0614     const QString dirPath = tempPath();
0615     connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems2);
0616     const QString path = dirPath + "toplevelfile_2";
0617     const QString newPath = dirPath + "toplevelfile_2.renamed.cpp";
0618 
0619     KIO::SimpleJob *job = KIO::rename(QUrl::fromLocalFile(path), QUrl::fromLocalFile(newPath), KIO::HideProgressInfo);
0620     QVERIFY(job->exec());
0621 
0622     QSignalSpy spyRefreshItems(&m_dirLister, &KCoreDirLister::refreshItems);
0623     QVERIFY(spyRefreshItems.wait(2000));
0624     QTRY_COMPARE(m_refreshedItems2.count(), 1);
0625     QPair<KFileItem, KFileItem> entry = m_refreshedItems2.first();
0626     QCOMPARE(entry.first.url().toLocalFile(), path);
0627     QCOMPARE(entry.first.mimetype(), QString("application/octet-stream"));
0628     QCOMPARE(entry.second.url().toLocalFile(), newPath);
0629     QCOMPARE(entry.second.mimetype(), QString("text/x-c++src"));
0630     disconnect(&m_dirLister, nullptr, this, nullptr);
0631 
0632     // Let's see what KDirLister has in cache now
0633     KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(newPath));
0634     QVERIFY(!cachedItem.isNull());
0635     QCOMPARE(cachedItem.url().toLocalFile(), newPath);
0636     KFileItem oldCachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path));
0637     QVERIFY(oldCachedItem.isNull());
0638     m_refreshedItems2.clear();
0639 }
0640 
0641 void KDirListerTest::testRenameAndOverwrite() // has to be run after testRenameItem
0642 {
0643     // Rename toplevelfile_2.renamed.html to toplevelfile_2, overwriting it.
0644     const QString dirPath = tempPath();
0645     const QString path = dirPath + "toplevelfile_2";
0646     createTestFile(path);
0647 
0648     if (s_workaroundBrokenInotify) {
0649         org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(dirPath));
0650     }
0651 
0652     KFileItem existingItem;
0653     while (existingItem.isNull()) {
0654         QTest::qWait(100);
0655         existingItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path));
0656     };
0657     QCOMPARE(existingItem.url().toLocalFile(), path);
0658 
0659     m_refreshedItems.clear();
0660     connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems);
0661     const QString newPath = dirPath + "toplevelfile_2.renamed.cpp";
0662 
0663     KIO::SimpleJob *job = KIO::rename(QUrl::fromLocalFile(newPath), QUrl::fromLocalFile(path), KIO::Overwrite | KIO::HideProgressInfo);
0664     bool ok = job->exec();
0665     QVERIFY(ok);
0666 
0667     if (m_refreshedItems.isEmpty()) {
0668         QTRY_VERIFY(!m_refreshedItems.isEmpty()); // could come from KDirWatch or KDirNotify.
0669     }
0670 
0671     // Check that itemsDeleted was emitted -- preferably BEFORE refreshItems,
0672     // but we can't easily check that with QSignalSpy...
0673     QCOMPARE(m_dirLister.spyItemsDeleted.count(), 1);
0674 
0675     QCOMPARE(m_refreshedItems.count(), 1);
0676     QPair<KFileItem, KFileItem> entry = m_refreshedItems.first();
0677     QCOMPARE(entry.first.url().toLocalFile(), newPath);
0678     QCOMPARE(entry.second.url().toLocalFile(), path);
0679     disconnect(&m_dirLister, nullptr, this, nullptr);
0680 
0681     // Let's see what KDirLister has in cache now
0682     KFileItem cachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(path));
0683     QCOMPARE(cachedItem.url().toLocalFile(), path);
0684     KFileItem oldCachedItem = m_dirLister.findByUrl(QUrl::fromLocalFile(newPath));
0685     QVERIFY(oldCachedItem.isNull());
0686     m_refreshedItems.clear();
0687 }
0688 
0689 void KDirListerTest::testConcurrentListing()
0690 {
0691     const int origItemCount = m_items.count();
0692     QCOMPARE(fileCount(), origItemCount);
0693     m_items.clear();
0694     m_items2.clear();
0695 
0696     MyDirLister dirLister2;
0697 
0698     const QString path = tempPath();
0699 
0700     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0701     connect(&dirLister2, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2);
0702 
0703     // Before dirLister2 has time to emit the items, let's make m_dirLister move to another dir.
0704     // This reproduces the use case "clicking on a folder in dolphin iconview, and dirlister2
0705     // is the one used by the "folder panel". m_dirLister is going to list the subdir,
0706     // while dirLister2 wants to list the folder that m_dirLister has just left.
0707     dirLister2.stop(); // like dolphin does, noop.
0708     dirLister2.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
0709     m_dirLister.openUrl(QUrl::fromLocalFile(path + "subdir"), KDirLister::NoFlags);
0710 
0711     QCOMPARE(m_dirLister.spyStarted.count(), 1);
0712     QCOMPARE(m_dirLister.spyCompleted.count(), 0);
0713     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0);
0714     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0715     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0716     QCOMPARE(m_dirLister.spyClear.count(), 1);
0717 
0718 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0719     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0720 #endif
0721     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0722 
0723     QCOMPARE(m_items.count(), 0);
0724 
0725     QCOMPARE(dirLister2.spyStarted.count(), 1);
0726     QCOMPARE(dirLister2.spyCompleted.count(), 0);
0727     QCOMPARE(dirLister2.spyCompletedQUrl.count(), 0);
0728     QCOMPARE(dirLister2.spyCanceled.count(), 0);
0729     QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0);
0730     QCOMPARE(dirLister2.spyClear.count(), 1);
0731 
0732 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0733     QCOMPARE(dirLister2.spyClearQUrl.count(), 0);
0734 #endif
0735     QCOMPARE(dirLister2.spyClearDir.count(), 0);
0736 
0737     QCOMPARE(m_items2.count(), 0);
0738     QVERIFY(!m_dirLister.isFinished());
0739     QVERIFY(!dirLister2.isFinished());
0740 
0741     // then wait for completed
0742     qDebug("waiting for completed");
0743 
0744     // QCOMPARE(m_dirLister.spyStarted.count(), 1); // 2 when subdir is already in cache.
0745     QTRY_COMPARE(m_dirLister.spyCompleted.count(), 1);
0746     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1);
0747     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0748     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0749     QCOMPARE(m_dirLister.spyClear.count(), 1);
0750 
0751 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0752     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0753 #endif
0754     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0755 
0756     QCOMPARE(m_items.count(), 3);
0757 
0758     QTRY_COMPARE(dirLister2.spyStarted.count(), 1);
0759     QTRY_COMPARE(dirLister2.spyCompleted.count(), 1);
0760     QCOMPARE(dirLister2.spyCompletedQUrl.count(), 1);
0761     QCOMPARE(dirLister2.spyCanceled.count(), 0);
0762     QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0);
0763     QCOMPARE(dirLister2.spyClear.count(), 1);
0764 
0765 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0766     QCOMPARE(dirLister2.spyClearQUrl.count(), 0);
0767 #endif
0768     QCOMPARE(dirLister2.spyClearDir.count(), 0);
0769 
0770     QCOMPARE(m_items2.count(), origItemCount);
0771     if (!m_dirLister.isFinished()) { // false when an update is running because subdir is already in cache
0772         // TODO check why this fails QVERIFY(m_dirLister.spyCanceled.wait(1000));
0773         QTest::qWait(1000);
0774     }
0775 
0776     disconnect(&m_dirLister, nullptr, this, nullptr);
0777     disconnect(&dirLister2, nullptr, this, nullptr);
0778 }
0779 
0780 void KDirListerTest::testConcurrentHoldingListing()
0781 {
0782     // #167851.
0783     // A dirlister holding the items, and a second dirlister does
0784     // openUrl(reload) (which triggers updateDirectory())
0785     // and the first lister immediately does openUrl() (which emits cached items).
0786 
0787     testOpenUrl(); // ensure m_dirLister holds the items.
0788     const int origItemCount = m_items.count();
0789     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0790 
0791     m_items.clear();
0792     m_items2.clear();
0793     const QString path = tempPath();
0794     MyDirLister dirLister2;
0795     connect(&dirLister2, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2);
0796 
0797     dirLister2.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); // will start a list job
0798     QCOMPARE(dirLister2.spyStarted.count(), 1);
0799     QCOMPARE(dirLister2.spyCompleted.count(), 0);
0800     QCOMPARE(m_items.count(), 0);
0801     QCOMPARE(m_items2.count(), 0);
0802 
0803     qDebug("calling m_dirLister.openUrl");
0804     m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // should emit cached items, and then "join" the running listjob
0805     QCOMPARE(m_dirLister.spyStarted.count(), 1);
0806     QCOMPARE(m_dirLister.spyCompleted.count(), 0);
0807     QCOMPARE(m_items.count(), 0);
0808     QCOMPARE(m_items2.count(), 0);
0809 
0810     qDebug("waiting for completed");
0811     QTRY_COMPARE(dirLister2.spyStarted.count(), 1);
0812     QTRY_COMPARE(dirLister2.spyCompleted.count(), 1);
0813     QCOMPARE(dirLister2.spyCompletedQUrl.count(), 1);
0814     QCOMPARE(dirLister2.spyCanceled.count(), 0);
0815     QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0);
0816     QCOMPARE(dirLister2.spyClear.count(), 1);
0817 
0818 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0819     QCOMPARE(dirLister2.spyClearQUrl.count(), 0);
0820 #endif
0821     QCOMPARE(dirLister2.spyClearDir.count(), 0);
0822 
0823     QCOMPARE(m_items2.count(), origItemCount);
0824 
0825     QTRY_COMPARE(m_dirLister.spyStarted.count(), 1);
0826     QTRY_COMPARE(m_dirLister.spyCompleted.count(), 1);
0827     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1);
0828     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0829     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0830     QCOMPARE(m_dirLister.spyClear.count(), 1);
0831 
0832 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0833     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0834 #endif
0835     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0836 
0837     QVERIFY(dirLister2.isFinished());
0838     QVERIFY(m_dirLister.isFinished());
0839     disconnect(&m_dirLister, nullptr, this, nullptr);
0840     QCOMPARE(m_items.count(), origItemCount);
0841 }
0842 
0843 void KDirListerTest::testConcurrentListingAndStop()
0844 {
0845     m_items.clear();
0846     m_items2.clear();
0847 
0848     MyDirLister dirLister2;
0849 
0850     // Use a new tempdir for this test, so that we don't use the cache at all.
0851     QTemporaryDir tempDir(homeTmpDir());
0852     const QString path = tempDir.path() + '/';
0853     createTestFile(path + "file_1");
0854     createTestFile(path + "file_2");
0855     createTestFile(path + "file_3");
0856 
0857     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0858     connect(&dirLister2, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2);
0859 
0860     // Before m_dirLister has time to emit the items, let's make dirLister2 call stop().
0861     // This should not stop the list job for m_dirLister (#267709).
0862     dirLister2.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload);
0863     m_dirLister.openUrl(QUrl::fromLocalFile(path) /*, KDirLister::Reload*/);
0864 
0865     QCOMPARE(m_dirLister.spyStarted.count(), 1);
0866     QCOMPARE(m_dirLister.spyCompleted.count(), 0);
0867     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0);
0868     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0869     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0870     QCOMPARE(m_dirLister.spyClear.count(), 1);
0871 
0872 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0873     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0874 #endif
0875     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0876 
0877     QCOMPARE(m_items.count(), 0);
0878 
0879     QCOMPARE(dirLister2.spyStarted.count(), 1);
0880     QCOMPARE(dirLister2.spyCompleted.count(), 0);
0881     QCOMPARE(dirLister2.spyCompletedQUrl.count(), 0);
0882     QCOMPARE(dirLister2.spyCanceled.count(), 0);
0883     QCOMPARE(dirLister2.spyCanceledQUrl.count(), 0);
0884     QCOMPARE(dirLister2.spyClear.count(), 1);
0885 
0886 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0887     QCOMPARE(dirLister2.spyClearQUrl.count(), 0);
0888 #endif
0889     QCOMPARE(dirLister2.spyClearDir.count(), 0);
0890 
0891     QCOMPARE(m_items2.count(), 0);
0892     QVERIFY(!m_dirLister.isFinished());
0893     QVERIFY(!dirLister2.isFinished());
0894 
0895     dirLister2.stop();
0896 
0897     QCOMPARE(dirLister2.spyStarted.count(), 1);
0898     QCOMPARE(dirLister2.spyCompleted.count(), 0);
0899     QCOMPARE(dirLister2.spyCompletedQUrl.count(), 0);
0900     QCOMPARE(dirLister2.spyCanceled.count(), 1);
0901     QCOMPARE(dirLister2.spyCanceledQUrl.count(), 1);
0902     QCOMPARE(dirLister2.spyClear.count(), 1);
0903 
0904 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0905     QCOMPARE(dirLister2.spyClearQUrl.count(), 0);
0906 #endif
0907     QCOMPARE(dirLister2.spyClearDir.count(), 0);
0908 
0909     QCOMPARE(m_items2.count(), 0);
0910 
0911     // then wait for completed
0912     qDebug("waiting for completed");
0913     QTRY_COMPARE(m_items.count(), 3);
0914     QTRY_COMPARE(m_items2.count(), 0);
0915     QTRY_VERIFY(m_dirLister.isFinished());
0916 
0917     // QCOMPARE(m_dirLister.spyStarted.count(), 1); // 2 when in cache
0918     QCOMPARE(m_dirLister.spyCompleted.count(), 1);
0919     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 1);
0920     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
0921     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
0922     QCOMPARE(m_dirLister.spyClear.count(), 1);
0923 
0924 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0925     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
0926 #endif
0927     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
0928 
0929     disconnect(&m_dirLister, nullptr, this, nullptr);
0930 }
0931 
0932 void KDirListerTest::testDeleteListerEarly()
0933 {
0934     // Do the same again, it should behave the same, even with the items in the cache
0935     testOpenUrl();
0936 
0937     // Start a second lister, it will get a cached items job, but delete it before the job can run
0938     // qDebug() << "==========================================";
0939     {
0940         m_items.clear();
0941         const QString path = tempPath();
0942         MyDirLister secondDirLister;
0943         secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
0944         QVERIFY(!secondDirLister.isFinished());
0945     }
0946     // qDebug() << "==========================================";
0947 
0948     // Check if we didn't keep the deleted dirlister in one of our lists.
0949     // I guess the best way to do that is to just list the same dir again.
0950     testOpenUrl();
0951 }
0952 
0953 void KDirListerTest::testOpenUrlTwice()
0954 {
0955     // Calling openUrl(reload)+openUrl(normal) before listing even starts.
0956     const int origItemCount = m_items.count();
0957     m_items.clear();
0958     const QString path = tempPath();
0959     MyDirLister secondDirLister;
0960     connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
0961 
0962     secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); // will start
0963     QCOMPARE(secondDirLister.spyStarted.count(), 1);
0964     QCOMPARE(secondDirLister.spyCompleted.count(), 0);
0965 
0966     qDebug("calling openUrl again");
0967     secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // will stop + start
0968 
0969     qDebug("waiting for completed");
0970     QTRY_COMPARE(secondDirLister.spyStarted.count(), 2);
0971     QTRY_COMPARE(secondDirLister.spyCompleted.count(), 1);
0972     QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 1);
0973     QCOMPARE(secondDirLister.spyCanceled.count(), 0); // should not be emitted, see next test
0974     QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0);
0975     QCOMPARE(secondDirLister.spyClear.count(), 2);
0976 
0977 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
0978     QCOMPARE(secondDirLister.spyClearQUrl.count(), 0);
0979 #endif
0980     QCOMPARE(secondDirLister.spyClearDir.count(), 0);
0981 
0982     if (origItemCount) { // 0 if running this test separately
0983         QCOMPARE(m_items.count(), origItemCount);
0984     }
0985     QVERIFY(secondDirLister.isFinished());
0986     disconnect(&secondDirLister, nullptr, this, nullptr);
0987 }
0988 
0989 void KDirListerTest::testOpenUrlTwiceWithKeep()
0990 {
0991     // Calling openUrl(reload)+openUrl(keep) on a new dir,
0992     // before listing even starts (#177387)
0993     // Well, in 177387 the second openUrl call was made from within slotCanceled
0994     // called by the first openUrl
0995     // (slotLoadingFinished -> setCurrentItem -> expandToUrl -> listDir),
0996     // which messed things up in kdirlister (unexpected reentrancy).
0997     m_items.clear();
0998     const QString path = tempPath() + "newsubdir";
0999     QDir().mkdir(path);
1000     MyDirLister secondDirLister;
1001     connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1002 
1003     secondDirLister.openUrl(QUrl::fromLocalFile(path)); // will start a list job
1004     QCOMPARE(secondDirLister.spyStarted.count(), 1);
1005     QCOMPARE(secondDirLister.spyCanceled.count(), 0);
1006     QCOMPARE(secondDirLister.spyCompleted.count(), 0);
1007 
1008     qDebug("calling openUrl again");
1009     secondDirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::Keep); // stops and restarts the job
1010 
1011     qDebug("waiting for completed");
1012     QTRY_COMPARE(secondDirLister.spyStarted.count(), 2);
1013     QTRY_COMPARE(secondDirLister.spyCompleted.count(), 1);
1014     QCOMPARE(secondDirLister.spyCompletedQUrl.count(), 1);
1015     QCOMPARE(secondDirLister.spyCanceled.count(), 0); // should not be emitted, it led to recursion
1016     QCOMPARE(secondDirLister.spyCanceledQUrl.count(), 0);
1017     QCOMPARE(secondDirLister.spyClear.count(), 1);
1018 
1019 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1020     QCOMPARE(secondDirLister.spyClearQUrl.count(), 1);
1021 #endif
1022     QCOMPARE(secondDirLister.spyClearDir.count(), 1);
1023 
1024     QCOMPARE(m_items.count(), 0);
1025     QVERIFY(secondDirLister.isFinished());
1026     disconnect(&secondDirLister, nullptr, this, nullptr);
1027 
1028     QDir().remove(path);
1029 }
1030 
1031 void KDirListerTest::testOpenAndStop()
1032 {
1033     m_items.clear();
1034     const QString path = QStringLiteral("/"); // better not use a directory that we already listed!
1035     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1036 
1037     m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
1038     qDebug() << "Calling stop!";
1039     m_dirLister.stop(); // we should also test stop(QUrl::fromLocalFile(path))...
1040 
1041     QCOMPARE(m_dirLister.spyStarted.count(), 1); // The call to openUrl itself, emits started
1042     QCOMPARE(m_dirLister.spyCompleted.count(), 0); // we had time to stop before the job even started
1043     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0);
1044     QCOMPARE(m_dirLister.spyCanceled.count(), 1);
1045     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 1);
1046     QCOMPARE(m_dirLister.spyClear.count(), 1);
1047 
1048 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1049     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
1050 #endif
1051     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
1052 
1053     QCOMPARE(m_items.count(), 0); // we had time to stop before the job even started
1054     QVERIFY(m_dirLister.isFinished());
1055     disconnect(&m_dirLister, nullptr, this, nullptr);
1056 }
1057 
1058 // A bug in the decAutoUpdate/incAutoUpdate logic made KDirLister stop watching a directory for changes,
1059 // and never watch it again when opening it from the cache.
1060 void KDirListerTest::testBug211472()
1061 {
1062     m_items.clear();
1063 
1064     QTemporaryDir newDir(homeTmpDir());
1065     const QString path = newDir.path() + "/newsubdir/";
1066     QDir().mkdir(path);
1067     MyDirLister dirLister;
1068     connect(&dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1069 
1070     dirLister.openUrl(QUrl::fromLocalFile(path));
1071     QVERIFY(dirLister.spyCompleted.wait(1000));
1072     QVERIFY(dirLister.isFinished());
1073     QVERIFY(m_items.isEmpty());
1074 
1075     // This block is required to trigger bug 211472.
1076 
1077     // Go 'up' to the parent of 'newsubdir'.
1078     dirLister.openUrl(QUrl::fromLocalFile(newDir.path()));
1079     QVERIFY(dirLister.spyCompleted.wait(1000));
1080     QTRY_VERIFY(dirLister.isFinished());
1081     QTRY_VERIFY(!m_items.isEmpty());
1082     m_items.clear();
1083 
1084     // Create a file in 'newsubdir' while we are listing its parent dir.
1085     createTestFile(path + "newFile-1");
1086     // At this point, newsubdir is not used, so it's moved to the cache.
1087     // This happens in checkUpdate, called when receiving a notification for the cached dir,
1088     // this is why this unittest needs to create a test file in the subdir.
1089 
1090     // wait a second and ensure the list is still empty afterwards
1091     QTest::qWait(1000);
1092     QTRY_VERIFY(m_items.isEmpty());
1093 
1094     // Return to 'newsubdir'. It will be emitted from the cache, then an update will happen.
1095     dirLister.openUrl(QUrl::fromLocalFile(path));
1096     // Check that completed is emitted twice
1097     QVERIFY(dirLister.spyCompleted.wait(1000));
1098     QVERIFY(dirLister.spyCompleted.wait(1000));
1099     QTRY_VERIFY(dirLister.isFinished());
1100     QTRY_COMPARE(m_items.count(), 1);
1101     m_items.clear();
1102 
1103     // Now try to create a second file in 'newsubdir' and verify that the
1104     // dir lister notices it.
1105     QTest::qWait(1000); // We need a 1s timestamp difference on the dir, otherwise FAM won't notice anything.
1106 
1107     createTestFile(path + "newFile-2");
1108     QTRY_COMPARE(m_items.count(), 1);
1109 
1110     newDir.remove();
1111     QSignalSpy spyClear(&dirLister, qOverload<>(&KCoreDirLister::clear));
1112     QVERIFY(spyClear.wait(1000));
1113 }
1114 
1115 void KDirListerTest::testRenameCurrentDir() // #294445
1116 {
1117     m_items.clear();
1118 
1119     const QString path = tempPath() + "newsubdir-1";
1120     QVERIFY(QDir().mkdir(path));
1121     MyDirLister secondDirLister;
1122     connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1123 
1124     secondDirLister.openUrl(QUrl::fromLocalFile(path));
1125     QSignalSpy spyCompleted(&secondDirLister, qOverload<>(&KCoreDirLister::completed));
1126     QVERIFY(spyCompleted.wait(1000));
1127     QVERIFY(secondDirLister.isFinished());
1128     QVERIFY(m_items.empty());
1129     QCOMPARE(secondDirLister.rootItem().url().toLocalFile(), path);
1130 
1131     const QString newPath = tempPath() + "newsubdir-2";
1132     QVERIFY(QDir().rename(path, newPath));
1133     org::kde::KDirNotify::emitFileRenamed(QUrl::fromLocalFile(path), QUrl::fromLocalFile(newPath));
1134     QSignalSpy spyRedirection(&secondDirLister, qOverload<const QUrl &, const QUrl &>(&KCoreDirLister::redirection));
1135     QVERIFY(spyRedirection.wait(1000));
1136 
1137     // Check that the URL of the root item got updated
1138     QCOMPARE(secondDirLister.rootItem().url().toLocalFile(), newPath);
1139 
1140     disconnect(&secondDirLister, nullptr, this, nullptr);
1141     QDir().rmdir(newPath);
1142 }
1143 
1144 void KDirListerTest::slotOpenUrlOnRename(const QUrl &newUrl)
1145 {
1146     QVERIFY(m_dirLister.openUrl(newUrl));
1147 }
1148 
1149 // This tests for a crash if you connect redirects to openUrl, due
1150 // to internal data being inconsistently exposed.
1151 // Matches usage in gwenview.
1152 void KDirListerTest::testRenameCurrentDirOpenUrl()
1153 {
1154     m_items.clear();
1155     const QString path = tempPath() + "newsubdir-1/";
1156     QVERIFY(QDir().mkdir(path));
1157     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1158 
1159     m_dirLister.openUrl(QUrl::fromLocalFile(path));
1160     QSignalSpy spyCompleted(&m_dirLister, qOverload<>(&KCoreDirLister::completed));
1161     // Wait for the signal completed to be emitted
1162     QVERIFY(spyCompleted.wait(1000));
1163     QVERIFY(m_dirLister.isFinished());
1164 
1165     const QString newPath = tempPath() + "newsubdir-2";
1166     QVERIFY(QDir().rename(path, newPath));
1167 
1168     org::kde::KDirNotify::emitFileRenamed(QUrl::fromLocalFile(path), QUrl::fromLocalFile(newPath));
1169 
1170     // Connect the redirection to openURL, so that on a rename the new location is opened.
1171     // This matches usage in gwenview, and crashes
1172 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 80)
1173     connect(&m_dirLister, qOverload<const QUrl &>(&KCoreDirLister::redirection), this, &KDirListerTest::slotOpenUrlOnRename);
1174 #else
1175     connect(&m_dirLister, qOverload<const QUrl &, const QUrl &>(&KCoreDirLister::redirection), this, [this](const QUrl &, const QUrl &newUrl) {
1176         slotOpenUrlOnRename(newUrl);
1177     });
1178 #endif
1179 
1180     QTRY_VERIFY(m_dirLister.isFinished());
1181     disconnect(&m_dirLister, nullptr, this, nullptr);
1182     QDir().rmdir(newPath);
1183 }
1184 
1185 void KDirListerTest::testRedirection()
1186 {
1187     m_items.clear();
1188     const QUrl url(QStringLiteral("file://somemachine/"));
1189 
1190     if (!KProtocolInfo::isKnownProtocol(QStringLiteral("smb"))) {
1191         QSKIP("smb not installed");
1192     }
1193 
1194     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1195     // The call to openUrl itself, emits started
1196     m_dirLister.openUrl(url, KDirLister::NoFlags);
1197 
1198     QCOMPARE(m_dirLister.spyStarted.count(), 1);
1199     QCOMPARE(m_dirLister.spyCompleted.count(), 0);
1200     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0);
1201     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
1202     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
1203     QCOMPARE(m_dirLister.spyClear.count(), 1);
1204 
1205 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1206     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
1207 #endif
1208     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
1209 
1210     QCOMPARE(m_dirLister.spyRedirection.count(), 0);
1211     QCOMPARE(m_items.count(), 0);
1212     QVERIFY(!m_dirLister.isFinished());
1213 
1214     // then wait for the redirection signal
1215     qDebug("waiting for redirection");
1216     QTRY_COMPARE(m_dirLister.spyStarted.count(), 1);
1217     QCOMPARE(m_dirLister.spyCompleted.count(), 0); // we stopped before the listing.
1218     QCOMPARE(m_dirLister.spyCompletedQUrl.count(), 0);
1219     QCOMPARE(m_dirLister.spyCanceled.count(), 0);
1220     QCOMPARE(m_dirLister.spyCanceledQUrl.count(), 0);
1221     QTRY_COMPARE(m_dirLister.spyClear.count(), 2); // redirection cleared a second time (just in case...)
1222 
1223 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1224     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
1225 #endif
1226     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
1227 
1228     QTRY_COMPARE(m_dirLister.spyRedirection.count(), 1);
1229     QVERIFY(m_items.isEmpty());
1230     QVERIFY(!m_dirLister.isFinished());
1231 
1232     m_dirLister.stop(url);
1233     QVERIFY(!m_dirLister.isFinished());
1234     disconnect(&m_dirLister, nullptr, this, nullptr);
1235 }
1236 
1237 void KDirListerTest::testListEmptyDirFromCache() // #278431
1238 {
1239     m_items.clear();
1240 
1241     QTemporaryDir newDir(homeTmpDir());
1242     const QUrl url = QUrl::fromLocalFile(newDir.path());
1243 
1244     // List and watch an empty dir
1245     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1246     m_dirLister.openUrl(url);
1247     QSignalSpy spyCompleted(&m_dirLister, qOverload<>(&KCoreDirLister::completed));
1248     QVERIFY(spyCompleted.wait(1000));
1249     QVERIFY(m_dirLister.isFinished());
1250     QVERIFY(m_items.isEmpty());
1251 
1252     // List it with two more dirlisters (one will create a cached items job, the second should also benefit from it)
1253     MyDirLister secondDirLister;
1254     connect(&secondDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1255     secondDirLister.openUrl(url);
1256     MyDirLister thirdDirLister;
1257     connect(&thirdDirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1258     thirdDirLister.openUrl(url);
1259 
1260     // The point of this test is that (with DEBUG_CACHE enabled) it used to assert here
1261     // with "HUH? Lister KDirLister(0x7ffd1f044260) is supposed to be listing, but has no job!"
1262     // due to the if (!itemU->lstItems.isEmpty()) check which is now removed.
1263 
1264     QVERIFY(!secondDirLister.isFinished()); // we didn't go to the event loop yet
1265     QSignalSpy spySecondCompleted(&secondDirLister, qOverload<>(&KCoreDirLister::completed));
1266     QVERIFY(spySecondCompleted.wait(1000));
1267     if (!thirdDirLister.isFinished()) {
1268         QSignalSpy spyThirdCompleted(&thirdDirLister, qOverload<>(&KCoreDirLister::completed));
1269         QVERIFY(spyThirdCompleted.wait(1000));
1270     }
1271 }
1272 
1273 void KDirListerTest::testWatchingAfterCopyJob() // #331582
1274 {
1275     m_items.clear();
1276 
1277     QTemporaryDir newDir(homeTmpDir());
1278     const QString path = newDir.path() + '/';
1279 
1280     // List and watch an empty dir
1281     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1282     m_dirLister.openUrl(QUrl::fromLocalFile(path));
1283     QSignalSpy spyCompleted(&m_dirLister, qOverload<>(&KCoreDirLister::completed));
1284     QVERIFY(spyCompleted.wait(1000));
1285     QVERIFY(m_dirLister.isFinished());
1286     QVERIFY(m_items.isEmpty());
1287 
1288     // Create three subfolders.
1289     QVERIFY(QDir().mkdir(path + "New Folder"));
1290     QVERIFY(QDir().mkdir(path + "New Folder 1"));
1291     QVERIFY(QDir().mkdir(path + "New Folder 2"));
1292 
1293     QVERIFY(spyCompleted.wait(1000));
1294     QTRY_VERIFY(m_dirLister.isFinished());
1295     QTRY_COMPARE(m_items.count(), 3);
1296 
1297     // Create a new file and verify that the dir lister notices it.
1298     m_items.clear();
1299     createTestFile(path + QLatin1Char('a'));
1300     QVERIFY(spyCompleted.wait(1000));
1301     QTRY_VERIFY(m_dirLister.isFinished());
1302     QTRY_COMPARE(m_items.count(), 1);
1303 
1304     // Rename one of the subfolders.
1305     const QString oldPath = path + "New Folder 1";
1306     const QString newPath = path + "New Folder 1a";
1307 
1308     // NOTE: The following two lines are required to trigger the bug!
1309     KIO::Job *job = KIO::moveAs(QUrl::fromLocalFile(oldPath), QUrl::fromLocalFile(newPath), KIO::HideProgressInfo);
1310     job->exec();
1311 
1312     // Now try to create a second new file and verify that the
1313     // dir lister notices it.
1314     m_items.clear();
1315     createTestFile(path + QLatin1Char('b'));
1316 
1317     // This should end up in "KCoreDirListerCache::slotFileDirty"
1318     QTRY_COMPARE(m_items.count(), 1);
1319 
1320     newDir.remove();
1321     QSignalSpy clearSpy(&m_dirLister, qOverload<>(&KCoreDirLister::clear));
1322     QVERIFY(clearSpy.wait(1000));
1323 }
1324 
1325 void KDirListerTest::testRemoveWatchedDirectory()
1326 {
1327     m_items.clear();
1328 
1329     QTemporaryDir newDir(homeTmpDir());
1330     const QString path = newDir.path() + '/';
1331 
1332     // List and watch an empty dir
1333     connect(&m_dirLister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1334     m_dirLister.openUrl(QUrl::fromLocalFile(path));
1335     QSignalSpy spyCompleted(&m_dirLister, qOverload<>(&KCoreDirLister::completed));
1336     QVERIFY(spyCompleted.wait(1000));
1337     QTRY_VERIFY(m_dirLister.isFinished());
1338     QTRY_VERIFY(m_items.isEmpty());
1339 
1340     // Create a subfolder.
1341     const QString subDirPath = path + "abc";
1342     QVERIFY(QDir().mkdir(subDirPath));
1343 
1344     QVERIFY(spyCompleted.wait(1000));
1345     QTRY_VERIFY(m_dirLister.isFinished());
1346     QTRY_COMPARE(m_items.count(), 1);
1347     const KFileItem item = m_items.at(0);
1348 
1349     // Watch the subfolder for changes, independently.
1350     // This is what triggers the bug.
1351     // (Technically, this could become a KDirWatch unittest, but if one day we use QFSW, good to have the tests here)
1352     KDirWatch watcher;
1353     watcher.addDir(subDirPath);
1354 
1355     // Remove the subfolder.
1356     m_items.clear();
1357     QVERIFY(QDir().rmdir(path + "abc"));
1358 
1359     // This should trigger an update.
1360     QVERIFY(spyCompleted.wait(1000));
1361     QVERIFY(m_dirLister.isFinished());
1362     QCOMPARE(m_items.count(), 0);
1363     QCOMPARE(m_dirLister.spyItemsDeleted.count(), 1);
1364     const KFileItem deletedItem = m_dirLister.spyItemsDeleted.at(0).at(0).value<KFileItemList>().at(0);
1365     QCOMPARE(item, deletedItem);
1366 }
1367 
1368 void KDirListerTest::testDirPermissionChange()
1369 {
1370     QTemporaryDir tempDir(homeTmpDir());
1371 
1372     const QString path = tempDir.path() + '/';
1373     const QString subdir = path + QLatin1String("subdir");
1374     QVERIFY(QDir().mkdir(subdir));
1375 
1376     MyDirLister mylister;
1377     mylister.openUrl(QUrl::fromLocalFile(tempDir.path()));
1378     QSignalSpy spyCompleted(&mylister, qOverload<>(&KCoreDirLister::completed));
1379     QVERIFY(spyCompleted.wait(1000));
1380 
1381     KFileItemList list = mylister.items();
1382     QVERIFY(mylister.isFinished());
1383     QCOMPARE(list.count(), 1);
1384     QCOMPARE(mylister.rootItem().url().toLocalFile(), tempDir.path());
1385 
1386     const mode_t permissions = (S_IRUSR | S_IWUSR | S_IXUSR);
1387     KIO::SimpleJob *job = KIO::chmod(list.first().url(), permissions);
1388     QVERIFY(job->exec());
1389 
1390     QSignalSpy spyRefreshItems(&mylister, &KCoreDirLister::refreshItems);
1391     QVERIFY(spyRefreshItems.wait(2000));
1392 
1393     list = mylister.items();
1394     QCOMPARE(list.first().permissions(), permissions);
1395     QVERIFY(QDir().rmdir(subdir));
1396 }
1397 
1398 void KDirListerTest::slotNewItems(const KFileItemList &lst)
1399 {
1400     m_items += lst;
1401 }
1402 
1403 void KDirListerTest::slotNewItems2(const KFileItemList &lst)
1404 {
1405     m_items2 += lst;
1406 }
1407 
1408 void KDirListerTest::slotRefreshItems(const QList<QPair<KFileItem, KFileItem>> &lst)
1409 {
1410     m_refreshedItems += lst;
1411     Q_EMIT refreshItemsReceived();
1412 }
1413 
1414 void KDirListerTest::slotRefreshItems2(const QList<QPair<KFileItem, KFileItem>> &lst)
1415 {
1416     m_refreshedItems2 += lst;
1417 }
1418 
1419 void KDirListerTest::testCopyAfterListingAndMove() // #353195
1420 {
1421     const QString dirA = tempPath() + "a";
1422     QVERIFY(QDir().mkdir(dirA));
1423     const QString dirB = tempPath() + "b";
1424     QVERIFY(QDir().mkdir(dirB));
1425 
1426     // ensure m_dirLister holds the items.
1427     m_dirLister.openUrl(QUrl::fromLocalFile(tempPath()), KDirLister::NoFlags);
1428     QSignalSpy spyCompleted(&m_dirLister, qOverload<>(&KCoreDirLister::completed));
1429     QVERIFY(spyCompleted.wait());
1430 
1431     // Move b into a
1432     KIO::Job *moveJob = KIO::move(QUrl::fromLocalFile(dirB), QUrl::fromLocalFile(dirA));
1433     moveJob->setUiDelegate(nullptr);
1434     QVERIFY(moveJob->exec());
1435     QVERIFY(QFileInfo(tempPath() + "a/b").isDir());
1436 
1437     // Give some time to processPendingUpdates
1438     QTest::qWait(1000);
1439 
1440     // Copy folder a elsewhere
1441     const QString dest = tempPath() + "subdir";
1442     KIO::Job *copyJob = KIO::copy(QUrl::fromLocalFile(dirA), QUrl::fromLocalFile(dest));
1443     copyJob->setUiDelegate(nullptr);
1444     QVERIFY(copyJob->exec());
1445     QVERIFY(QFileInfo(tempPath() + "subdir/a/b").isDir());
1446 }
1447 
1448 void KDirListerTest::testRenameDirectory() // #401552
1449 {
1450     // Create the directory structure to reproduce the bug in a reliable way
1451     const QString dirW = tempPath() + "w";
1452     QVERIFY(QDir().mkdir(dirW));
1453     const QString dirW1 = tempPath() + "w/Files";
1454     QVERIFY(QDir().mkdir(dirW1));
1455     const QString dirW2 = tempPath() + "w/Files/Files";
1456     QVERIFY(QDir().mkdir(dirW2));
1457     // Place some empty files in each directory
1458     for (int i = 0; i < 50; i++) {
1459         createSimpleFile(dirW + QString("t_%1").arg(i));
1460     }
1461     for (int i = 0; i < 50; i++) {
1462         createSimpleFile(dirW + QString("z_%1").arg(i));
1463     }
1464     // Place some empty files with prefix Files in w. Note that / is missing.
1465     for (int i = 0; i < 50; i++) {
1466         createSimpleFile(dirW1 + QString("t_%1").arg(i));
1467     }
1468     for (int i = 0; i < 50; i++) {
1469         createSimpleFile(dirW1 + QString("z_%1").arg(i));
1470     }
1471     // Place some empty files with prefix Files in w/Files. Note that / is missing.
1472     for (int i = 0; i < 50; i++) {
1473         createSimpleFile(dirW2 + QString("t_%1").arg(i));
1474     }
1475     for (int i = 0; i < 50; i++) {
1476         createSimpleFile(dirW2 + QString("z_%1").arg(i));
1477     }
1478     // Listen to the w directory
1479     m_dirLister.openUrl(QUrl::fromLocalFile(dirW), KDirLister::NoFlags);
1480 
1481     // Try to reproduce the bug #401552 renaming the w directory several times if needed
1482     const QStringList dirs = {dirW + "___", dirW + QLatin1Char('_'), dirW + "______", dirW + "_c", dirW + "___", dirW + "_________"};
1483 
1484     QString currDir = dirW;
1485     KIO::SimpleJob *job = nullptr;
1486     // Connect the redirection to openURL, so that on a rename the new location is opened.
1487 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 80)
1488     connect(&m_dirLister, qOverload<const QUrl &>(&KCoreDirLister::redirection), this, &KDirListerTest::slotOpenUrlOnRename);
1489 #else
1490     connect(&m_dirLister, qOverload<const QUrl &, const QUrl &>(&KCoreDirLister::redirection), this, [this](const QUrl &, const QUrl &newUrl) {
1491         slotOpenUrlOnRename(newUrl);
1492     });
1493 #endif
1494 
1495     for (int i = 0; i < dirs.size(); i++) {
1496         // Wait for the listener to get all files
1497         QTRY_VERIFY(m_dirLister.isFinished());
1498         // Do the rename
1499         QString newDir = dirs.at(i);
1500         job = KIO::rename(QUrl::fromLocalFile(currDir), QUrl::fromLocalFile(newDir), KIO::HideProgressInfo);
1501         QVERIFY2(job->exec(), qPrintable(job->errorString()));
1502         QTest::qWait(500); // Without the delay the crash doesn't happen
1503         currDir = newDir;
1504     }
1505     disconnect(&m_dirLister, nullptr, this, nullptr);
1506 }
1507 
1508 void KDirListerTest::testRequestMimeType()
1509 {
1510     // Use a new tempdir and lister instance for this test, so that we don't use any cache at all.
1511     QTemporaryDir tempDir(homeTmpDir());
1512     QString path = tempDir.path() + QLatin1Char('/');
1513 
1514     createTestFile(path + "/file_1");
1515     createTestFile(path + "/file_2.txt");
1516     createTestFile(path + "/file_3.cpp");
1517     createTestFile(path + "/file_3.md");
1518 
1519     MyDirLister lister;
1520     // Explicitly set requestMimeTypeWhileListing to false so we know what state
1521     // it is in.
1522     lister.setRequestMimeTypeWhileListing(false);
1523     lister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
1524 
1525     QTRY_VERIFY(lister.isFinished());
1526 
1527     auto items = lister.items();
1528     for (auto item : std::as_const(items)) {
1529         QVERIFY(!item.isMimeTypeKnown());
1530     }
1531 
1532     // Verify that the mime types are what we expect them to be
1533     QCOMPARE(items[0].mimetype(), QStringLiteral("application/octet-stream"));
1534     QCOMPARE(items[1].mimetype(), QStringLiteral("text/plain"));
1535     QCOMPARE(items[2].mimetype(), QStringLiteral("text/x-c++src"));
1536     QCOMPARE(items[3].mimetype(), QStringLiteral("text/markdown"));
1537 
1538     lister.setRequestMimeTypeWhileListing(true);
1539     lister.openUrl(QUrl::fromLocalFile(path), KDirLister::Reload);
1540 
1541     QTRY_VERIFY(lister.isFinished());
1542 
1543     // If requestMimeTypeWhileListing is on, we should know the mime type of
1544     // items when they have been listed.
1545     items = lister.items();
1546     for (auto item : std::as_const(items)) {
1547         QVERIFY(item.isMimeTypeKnown());
1548     }
1549 
1550     // Verify that the mime types are what we expect them to be
1551     QCOMPARE(items[0].mimetype(), QStringLiteral("application/octet-stream"));
1552     QCOMPARE(items[1].mimetype(), QStringLiteral("text/plain"));
1553     QCOMPARE(items[2].mimetype(), QStringLiteral("text/x-c++src"));
1554     QCOMPARE(items[3].mimetype(), QStringLiteral("text/markdown"));
1555 }
1556 
1557 void KDirListerTest::testMimeFilter_data()
1558 {
1559     QTest::addColumn<QStringList>("files");
1560     QTest::addColumn<QStringList>("mimeTypes");
1561     QTest::addColumn<QStringList>("filteredFiles");
1562 
1563     const QStringList files = {"bla.txt", "main.cpp", "main.c", "image.jpeg"};
1564 
1565     QTest::newRow("single_file_exact_mimetype") << files << QStringList{"text/x-c++src"} << QStringList{"main.cpp"};
1566     QTest::newRow("inherited_mimetype") << files << QStringList{"text/plain"} << QStringList{"bla.txt", "main.cpp", "main.c"};
1567     QTest::newRow("no_match") << files << QStringList{"audio/flac"} << QStringList{};
1568 }
1569 
1570 void KDirListerTest::testMimeFilter()
1571 {
1572     // Use a new tempdir and lister instance for this test, so that we don't use any cache at all.
1573     QTemporaryDir tempDir(homeTmpDir());
1574     QString path = tempDir.path() + '/';
1575 
1576     QFETCH(QStringList, files);
1577     QFETCH(QStringList, mimeTypes);
1578     QFETCH(QStringList, filteredFiles);
1579 
1580     for (const QString &fileName : files) {
1581         createTestFile(path + fileName);
1582     }
1583 
1584     MyDirLister lister;
1585     lister.setMimeFilter(mimeTypes);
1586     lister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
1587 
1588     QSignalSpy spyCompleted(&lister, qOverload<>(&KCoreDirLister::completed));
1589     QVERIFY(spyCompleted.wait(1000));
1590 
1591     QCOMPARE(lister.items().size(), filteredFiles.size());
1592 
1593     const auto items = lister.items();
1594     for (const auto &item : items) {
1595         QVERIFY(filteredFiles.indexOf(item.name()) != -1);
1596     }
1597 }
1598 
1599 void KDirListerTest::testDeleteCurrentDir()
1600 {
1601     // ensure m_dirLister holds the items.
1602     m_dirLister.openUrl(QUrl::fromLocalFile(tempPath()), KDirLister::NoFlags);
1603     m_dirLister.clearSpies();
1604     KIO::DeleteJob *job = KIO::del(QUrl::fromLocalFile(tempPath()), KIO::HideProgressInfo);
1605     bool ok = job->exec();
1606     QVERIFY(ok);
1607     QTRY_COMPARE(m_dirLister.spyClear.count(), 1);
1608 
1609 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79)
1610     QCOMPARE(m_dirLister.spyClearQUrl.count(), 0);
1611 #endif
1612     QCOMPARE(m_dirLister.spyClearDir.count(), 0);
1613 
1614     QList<QUrl> deletedUrls;
1615     for (int i = 0; i < m_dirLister.spyItemsDeleted.count(); ++i) {
1616         deletedUrls += m_dirLister.spyItemsDeleted[i][0].value<KFileItemList>().urlList();
1617     }
1618     // qDebug() << deletedUrls;
1619     QUrl currentDirUrl = QUrl::fromLocalFile(tempPath()).adjusted(QUrl::StripTrailingSlash);
1620     // Sometimes I get ("current/subdir", "current") here, but that seems ok.
1621     QVERIFY(deletedUrls.contains(currentDirUrl));
1622 }
1623 
1624 void KDirListerTest::testForgetDir()
1625 {
1626     QTemporaryDir tempDir(homeTmpDir());
1627     QString path = tempDir.path();
1628     createTestFile(path + "/file_1");
1629 
1630     m_dirLister.openUrl(QUrl::fromLocalFile(path), KDirLister::Keep);
1631     QVERIFY(m_dirLister.spyCompleted.wait());
1632 
1633     m_dirLister.forgetDirs(QUrl::fromLocalFile(path));
1634 
1635     QSignalSpy addedSpy(&m_dirLister, &MyDirLister::itemsAdded);
1636     createTestFile(path + "/file_2");
1637     QVERIFY(!addedSpy.wait(1000)); // to allow for KDirWatch's internal 500ms timer
1638 }
1639 
1640 int KDirListerTest::fileCount() const
1641 {
1642     return QDir(tempPath()).entryList(QDir::AllEntries | QDir::NoDotAndDotDot).count();
1643 }
1644 
1645 void KDirListerTest::createSimpleFile(const QString &fileName)
1646 {
1647     QFile file(fileName);
1648     QVERIFY(file.open(QIODevice::WriteOnly));
1649     file.write(QByteArray("foo"));
1650     file.close();
1651 }
1652 
1653 void KDirListerTest::fillDirLister2(MyDirLister &lister, const QString &path)
1654 {
1655     m_items2.clear();
1656     connect(&lister, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems2);
1657     connect(&m_dirLister, &KCoreDirLister::refreshItems, this, &KDirListerTest::slotRefreshItems2);
1658     lister.openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags);
1659     QTRY_VERIFY(lister.isFinished());
1660 }
1661 
1662 void KDirListerTest::waitUntilMTimeChange(const QString &path)
1663 {
1664     // Wait until the current second is more than the file's mtime
1665     // otherwise this change will go unnoticed
1666 
1667     QFileInfo fi(path);
1668     QVERIFY(fi.exists());
1669     const QDateTime mtime = fi.lastModified();
1670     waitUntilAfter(mtime);
1671 }
1672 
1673 void KDirListerTest::waitUntilAfter(const QDateTime &ctime)
1674 {
1675     int totalWait = 0;
1676     QDateTime now;
1677     Q_FOREVER {
1678         now = QDateTime::currentDateTime();
1679         if (now.toSecsSinceEpoch() == ctime.toSecsSinceEpoch()) { // truncate milliseconds
1680             totalWait += 50;
1681             QTest::qWait(50);
1682         } else {
1683             QVERIFY(now > ctime); // can't go back in time ;)
1684             QTest::qWait(50); // be safe
1685             break;
1686         }
1687     }
1688     // if (totalWait > 0)
1689     qDebug() << "Waited" << totalWait << "ms so that now" << now.toString(Qt::ISODate) << "is >" << ctime.toString(Qt::ISODate);
1690 }
1691 
1692 // A bug in the decAutoUpdate/incAutoUpdate logic made KDirLister stop watching a directory for changes,
1693 // and stop watching a directory because a separate lister left a directory open in another lister
1694 void KDirListerTest::testBug386763()
1695 {
1696     QTemporaryDir newDir(homeTmpDir());
1697     const QString path = newDir.path() + "/newsubdir/";
1698     const QString otherpath = newDir.path() + "/othersubdir/";
1699 
1700     QDir().mkdir(path);
1701     MyDirLister dirLister;
1702     dirLister.openUrl(QUrl::fromLocalFile(path));
1703 
1704     // second lister opening same dir
1705     MyDirLister dirLister2;
1706     dirLister2.openUrl(QUrl::fromLocalFile(path));
1707     QCOMPARE(dirLister2.spyCompleted.count(), 0);
1708 
1709     connect(&dirLister2, &KCoreDirLister::newItems, this, &KDirListerTest::slotNewItems);
1710     QVERIFY(dirLister.spyCompleted.wait(500));
1711     QVERIFY(dirLister.isFinished());
1712     QVERIFY(m_items.isEmpty());
1713 
1714     // first lister opening another dir
1715     dirLister.openUrl(QUrl::fromLocalFile(otherpath));
1716 
1717     // Create a file in 'newsubdir' while still opened in dirLister2
1718     // bug was that the watch on ’newsubdir’ when dirLister left this dir
1719     // eventhough dirLister2 is still listing it
1720     QCOMPARE(dirLister2.spyCompleted.count(), 1);
1721     createTestFile(path + "newFile-1");
1722 
1723     QTRY_COMPARE(m_items.count(), 1);
1724     QVERIFY(KDirWatch::self()->contains(path));
1725 
1726     dirLister2.openUrl(QUrl::fromLocalFile(otherpath));
1727     // checks we still watch the old path when second lister leaves it as it should be now in cache
1728     QVERIFY(KDirWatch::self()->contains(path));
1729 
1730     newDir.remove();
1731 }
1732 
1733 void KDirListerTest::testCacheEviction()
1734 {
1735     QTemporaryDir newDir(homeTmpDir());
1736 
1737     MyDirLister dirLister;
1738     dirLister.openUrl(QUrl::fromLocalFile(newDir.path()));
1739     QVERIFY(dirLister.spyCompleted.wait(500));
1740     QVERIFY(dirLister.isFinished());
1741     QVERIFY(KDirWatch::self()->contains(newDir.path()));
1742 
1743     for (int i = 0; i < 12; i++) {
1744         const QString newDirPath = newDir.path() + QString("dir_%1").arg(i);
1745         QVERIFY(QDir().mkdir(newDirPath));
1746 
1747         dirLister.openUrl(QUrl::fromLocalFile(newDirPath));
1748         QVERIFY(dirLister.spyCompleted.wait(500));
1749         QVERIFY(dirLister.isFinished());
1750         QVERIFY(KDirWatch::self()->contains(newDirPath));
1751     }
1752 
1753     // watches were removed as the dirItem were evicted from cache
1754     QVERIFY(!KDirWatch::self()->contains(newDir.path()));
1755     QVERIFY(!KDirWatch::self()->contains(newDir.path() + QString("dir_0")));
1756     QVERIFY(KDirWatch::self()->contains(newDir.path() + QString("dir_1")));
1757 }
1758 
1759 #include "moc_kdirlistertest.cpp"