File indexing completed on 2024-03-24 15:33:28

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