File indexing completed on 2024-04-14 07:29:52

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