File indexing completed on 2023-09-24 07:59:53
0001 /* 0002 This file is part of the KDE libraries 0003 0004 SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include <kdirwatch.h> 0010 0011 #include <QDebug> 0012 #include <QDir> 0013 #include <QFileInfo> 0014 #include <QSignalSpy> 0015 #include <QTemporaryDir> 0016 #include <QTest> 0017 #include <QThread> 0018 #include <sys/stat.h> 0019 #ifdef Q_OS_UNIX 0020 #include <unistd.h> // ::link() 0021 #endif 0022 0023 #include "kcoreaddons_debug.h" 0024 #include "kdirwatch_test_utils.h" 0025 0026 using namespace KDirWatchTestUtils; 0027 0028 // Debugging notes: to see which inotify signals are emitted, either set s_verboseDebug=true 0029 // at the top of kdirwatch.cpp, or use the command-line tool "inotifywait -m /path" 0030 0031 class KDirWatch_UnitTest : public QObject 0032 { 0033 Q_OBJECT 0034 public: 0035 KDirWatch_UnitTest() 0036 { 0037 // Speed up the test by making the kdirwatch timer (to compress changes) faster 0038 qputenv("KDIRWATCH_POLLINTERVAL", "50"); 0039 qputenv("KDIRWATCH_METHOD", KDIRWATCH_TEST_METHOD); 0040 s_staticObjectUsingSelf(); 0041 0042 m_path = m_tempDir.path() + QLatin1Char('/'); 0043 KDirWatch *dirW = &s_staticObject()->m_dirWatch; 0044 m_stat = dirW->internalMethod() == KDirWatch::Stat; 0045 m_slow = (dirW->internalMethod() == KDirWatch::FAM || m_stat); 0046 qCDebug(KCOREADDONS_DEBUG) << "Using method" << methodToString(dirW->internalMethod()); 0047 } 0048 0049 private Q_SLOTS: // test methods 0050 void initTestCase() 0051 { 0052 QFileInfo pathInfo(m_path); 0053 QVERIFY(pathInfo.isDir() && pathInfo.isWritable()); 0054 0055 // By creating the files upfront, we save waiting a full second for an mtime change 0056 createFile(m_path + QLatin1String("ExistingFile")); 0057 createFile(m_path + QLatin1String("TestFile")); 0058 createFile(m_path + QLatin1String("nested_0")); 0059 createFile(m_path + QLatin1String("nested_1")); 0060 0061 s_staticObject()->m_dirWatch.addFile(m_path + QLatin1String("ExistingFile")); 0062 } 0063 void touchOneFile(); 0064 void touch1000Files(); 0065 void watchAndModifyOneFile(); 0066 void removeAndReAdd(); 0067 void watchNonExistent(); 0068 void watchNonExistentWithSingleton(); 0069 void testDelete(); 0070 void testDeleteAndRecreateFile(); 0071 void testDeleteAndRecreateDir(); 0072 void testMoveTo(); 0073 void nestedEventLoop(); 0074 void testHardlinkChange(); 0075 void stopAndRestart(); 0076 void testRefcounting(); 0077 0078 protected Q_SLOTS: // internal slots 0079 void nestedEventLoopSlot(); 0080 0081 private: 0082 QList<QVariantList> waitForDirtySignal(KDirWatch &watch, int expected); 0083 QList<QVariantList> waitForDeletedSignal(KDirWatch &watch, int expected); 0084 bool waitForOneSignal(KDirWatch &watch, const char *sig, const QString &path); 0085 bool waitForRecreationSignal(KDirWatch &watch, const QString &path); 0086 bool verifySignalPath(QSignalSpy &spy, const char *sig, const QString &expectedPath); 0087 QString createFile(int num); 0088 void createFile(const QString &file) 0089 { 0090 KDirWatchTestUtils::createFile(file, m_slow); 0091 } 0092 void removeFile(int num); 0093 void appendToFile(const QString &path); 0094 void appendToFile(int num); 0095 0096 QTemporaryDir m_tempDir; 0097 QString m_path; 0098 bool m_slow; 0099 bool m_stat; 0100 }; 0101 0102 QTEST_MAIN(KDirWatch_UnitTest) 0103 0104 static const int s_maxTries = 50; 0105 0106 // helper method: create a file (identified by number) 0107 QString KDirWatch_UnitTest::createFile(int num) 0108 { 0109 const QString fileName = QLatin1String(s_filePrefix) + QString::number(num); 0110 KDirWatchTestUtils::createFile(m_path + fileName, m_slow); 0111 return m_path + fileName; 0112 } 0113 0114 // helper method: delete a file (identified by number) 0115 void KDirWatch_UnitTest::removeFile(int num) 0116 { 0117 const QString fileName = QLatin1String(s_filePrefix) + QString::number(num); 0118 QFile::remove(m_path + fileName); 0119 } 0120 0121 // helper method: modifies a file 0122 void KDirWatch_UnitTest::appendToFile(const QString &path) 0123 { 0124 QVERIFY(QFile::exists(path)); 0125 waitUntilMTimeChange(path); 0126 0127 QFile file(path); 0128 QVERIFY(file.open(QIODevice::Append | QIODevice::WriteOnly)); 0129 file.write(QByteArray("foobar")); 0130 file.close(); 0131 } 0132 0133 // helper method: modifies a file (identified by number) 0134 void KDirWatch_UnitTest::appendToFile(int num) 0135 { 0136 const QString fileName = QLatin1String(s_filePrefix) + QString::number(num); 0137 appendToFile(m_path + fileName); 0138 } 0139 0140 static QString removeTrailingSlash(const QString &path) 0141 { 0142 if (path.endsWith(QLatin1Char('/'))) { 0143 return path.left(path.length() - 1); 0144 } else { 0145 return path; 0146 } 0147 } 0148 0149 // helper method 0150 QList<QVariantList> KDirWatch_UnitTest::waitForDirtySignal(KDirWatch &watch, int expected) 0151 { 0152 QSignalSpy spyDirty(&watch, &KDirWatch::dirty); 0153 int numTries = 0; 0154 // Give time for KDirWatch to notify us 0155 while (spyDirty.count() < expected) { 0156 if (++numTries > s_maxTries) { 0157 qWarning() << "Timeout waiting for KDirWatch. Got" << spyDirty.count() << "dirty() signals, expected" << expected; 0158 return std::move(spyDirty); 0159 } 0160 spyDirty.wait(50); 0161 } 0162 return std::move(spyDirty); 0163 } 0164 0165 bool KDirWatch_UnitTest::waitForOneSignal(KDirWatch &watch, const char *sig, const QString &path) 0166 { 0167 const QString expectedPath = removeTrailingSlash(path); 0168 while (true) { 0169 QSignalSpy spyDirty(&watch, sig); 0170 int numTries = 0; 0171 // Give time for KDirWatch to notify us 0172 while (spyDirty.isEmpty()) { 0173 if (++numTries > s_maxTries) { 0174 qWarning() << "Timeout waiting for KDirWatch signal" << QByteArray(sig).mid(1) << "(" << path << ")"; 0175 return false; 0176 } 0177 spyDirty.wait(50); 0178 } 0179 return verifySignalPath(spyDirty, sig, expectedPath); 0180 } 0181 } 0182 0183 bool KDirWatch_UnitTest::verifySignalPath(QSignalSpy &spy, const char *sig, const QString &expectedPath) 0184 { 0185 for (int i = 0; i < spy.count(); ++i) { 0186 const QString got = spy[i][0].toString(); 0187 if (got == expectedPath) { 0188 return true; 0189 } 0190 if (got.startsWith(expectedPath + QLatin1Char('/'))) { 0191 qCDebug(KCOREADDONS_DEBUG) << "Ignoring (inotify) notification of" << (sig + 1) << '(' << got << ')'; 0192 continue; 0193 } 0194 qWarning() << "Expected" << sig << '(' << expectedPath << ')' << "but got" << sig << '(' << got << ')'; 0195 return false; 0196 } 0197 return false; 0198 } 0199 0200 bool KDirWatch_UnitTest::waitForRecreationSignal(KDirWatch &watch, const QString &path) 0201 { 0202 // When watching for a deleted + created signal pair, the two might come so close that 0203 // using waitForOneSignal will miss the created signal. This function monitors both all 0204 // the time to ensure both are received. 0205 // 0206 // In addition, it allows dirty() to be emitted (for that same path) as an alternative 0207 0208 const QString expectedPath = removeTrailingSlash(path); 0209 QSignalSpy spyDirty(&watch, &KDirWatch::dirty); 0210 QSignalSpy spyDeleted(&watch, &KDirWatch::deleted); 0211 QSignalSpy spyCreated(&watch, &KDirWatch::created); 0212 0213 int numTries = 0; 0214 while (spyDeleted.isEmpty() && spyDirty.isEmpty()) { 0215 if (++numTries > s_maxTries) { 0216 return false; 0217 } 0218 spyDeleted.wait(50); 0219 while (!spyDirty.isEmpty()) { 0220 if (spyDirty.at(0).at(0).toString() != expectedPath) { // unrelated 0221 spyDirty.removeFirst(); 0222 } 0223 } 0224 } 0225 if (!spyDirty.isEmpty()) { 0226 return true; 0227 } 0228 0229 // Don't bother waiting for the created signal if the signal spy already received a signal. 0230 if (spyCreated.isEmpty() && !spyCreated.wait(50 * s_maxTries)) { 0231 qWarning() << "Timeout waiting for KDirWatch signal created(QString) (" << path << ")"; 0232 return false; 0233 } 0234 0235 return verifySignalPath(spyDeleted, "deleted(QString)", expectedPath) && verifySignalPath(spyCreated, "created(QString)", expectedPath); 0236 } 0237 0238 QList<QVariantList> KDirWatch_UnitTest::waitForDeletedSignal(KDirWatch &watch, int expected) 0239 { 0240 QSignalSpy spyDeleted(&watch, &KDirWatch::created); 0241 int numTries = 0; 0242 // Give time for KDirWatch to notify us 0243 while (spyDeleted.count() < expected) { 0244 if (++numTries > s_maxTries) { 0245 qWarning() << "Timeout waiting for KDirWatch. Got" << spyDeleted.count() << "deleted() signals, expected" << expected; 0246 return std::move(spyDeleted); 0247 } 0248 spyDeleted.wait(50); 0249 } 0250 return std::move(spyDeleted); 0251 } 0252 0253 void KDirWatch_UnitTest::touchOneFile() // watch a dir, create a file in it 0254 { 0255 KDirWatch watch; 0256 watch.addDir(m_path); 0257 watch.startScan(); 0258 0259 waitUntilMTimeChange(m_path); 0260 0261 // dirty(the directory) should be emitted. 0262 QSignalSpy spyCreated(&watch, &KDirWatch::created); 0263 createFile(0); 0264 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); 0265 QCOMPARE(spyCreated.count(), 0); // "This is not emitted when creating a file is created in a watched directory." 0266 0267 removeFile(0); 0268 } 0269 0270 void KDirWatch_UnitTest::touch1000Files() 0271 { 0272 KDirWatch watch; 0273 watch.addDir(m_path); 0274 watch.startScan(); 0275 0276 waitUntilMTimeChange(m_path); 0277 0278 const int fileCount = 100; 0279 for (int i = 0; i < fileCount; ++i) { 0280 createFile(i); 0281 } 0282 0283 QList<QVariantList> spy = waitForDirtySignal(watch, fileCount); 0284 if (watch.internalMethod() == KDirWatch::INotify) { 0285 QVERIFY(spy.count() >= fileCount); 0286 } else { 0287 // More stupid backends just see one mtime change on the directory 0288 QVERIFY(spy.count() >= 1); 0289 } 0290 0291 for (int i = 0; i < fileCount; ++i) { 0292 removeFile(i); 0293 } 0294 } 0295 0296 void KDirWatch_UnitTest::watchAndModifyOneFile() // watch a specific file, and modify it 0297 { 0298 KDirWatch watch; 0299 const QString existingFile = m_path + QLatin1String("ExistingFile"); 0300 watch.addFile(existingFile); 0301 watch.startScan(); 0302 if (m_slow) { 0303 waitUntilNewSecond(); 0304 } 0305 appendToFile(existingFile); 0306 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), existingFile)); 0307 } 0308 0309 void KDirWatch_UnitTest::removeAndReAdd() 0310 { 0311 KDirWatch watch; 0312 watch.addDir(m_path); 0313 // This triggers bug #374075. 0314 watch.addDir(QStringLiteral(":/kio5/newfile-templates")); 0315 watch.startScan(); 0316 if (watch.internalMethod() != KDirWatch::INotify) { 0317 waitUntilNewSecond(); // necessary for mtime checks in scanEntry 0318 } 0319 createFile(0); 0320 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); 0321 0322 // Just like KDirLister does: remove the watch, then re-add it. 0323 watch.removeDir(m_path); 0324 watch.addDir(m_path); 0325 if (watch.internalMethod() != KDirWatch::INotify) { 0326 waitUntilMTimeChange(m_path); // necessary for FAM and QFSWatcher 0327 } 0328 createFile(1); 0329 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); 0330 } 0331 0332 void KDirWatch_UnitTest::watchNonExistent() 0333 { 0334 KDirWatch watch; 0335 // Watch "subdir", that doesn't exist yet 0336 const QString subdir = m_path + QLatin1String("subdir"); 0337 QVERIFY(!QFile::exists(subdir)); 0338 watch.addDir(subdir); 0339 watch.startScan(); 0340 0341 if (m_slow) { 0342 waitUntilNewSecond(); 0343 } 0344 0345 // Now create it, KDirWatch should emit created() 0346 qCDebug(KCOREADDONS_DEBUG) << "Creating" << subdir; 0347 QDir().mkdir(subdir); 0348 0349 QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), subdir)); 0350 0351 KDirWatch::statistics(); 0352 0353 // Play with addDir/removeDir, just for fun 0354 watch.addDir(subdir); 0355 watch.removeDir(subdir); 0356 watch.addDir(subdir); 0357 0358 // Now watch files that don't exist yet 0359 const QString file = subdir + QLatin1String("/0"); 0360 watch.addFile(file); // doesn't exist yet 0361 const QString file1 = subdir + QLatin1String("/1"); 0362 watch.addFile(file1); // doesn't exist yet 0363 watch.removeFile(file1); // forget it again 0364 0365 KDirWatch::statistics(); 0366 0367 QVERIFY(!QFile::exists(file)); 0368 // Now create it, KDirWatch should emit created 0369 qCDebug(KCOREADDONS_DEBUG) << "Creating" << file; 0370 createFile(file); 0371 QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), file)); 0372 0373 appendToFile(file); 0374 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file)); 0375 0376 // Create the file after all; we're not watching for it, but the dir will emit dirty 0377 createFile(file1); 0378 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), subdir)); 0379 } 0380 0381 void KDirWatch_UnitTest::watchNonExistentWithSingleton() 0382 { 0383 const QString file = QLatin1String("/root/.ssh/authorized_keys"); 0384 KDirWatch::self()->addFile(file); 0385 // When running this test in KDIRWATCH_METHOD=QFSWatch, or when FAM is not available 0386 // and we fallback to qfswatch when inotify fails above, we end up creating the fsWatch 0387 // in the kdirwatch singleton. Bug 261541 discovered that Qt hanged when deleting fsWatch 0388 // once QCoreApp was gone, this is what this test is about. 0389 } 0390 0391 void KDirWatch_UnitTest::testDelete() 0392 { 0393 const QString file1 = m_path + QLatin1String("del"); 0394 if (!QFile::exists(file1)) { 0395 createFile(file1); 0396 } 0397 waitUntilMTimeChange(file1); 0398 0399 // Watch the file, then delete it, KDirWatch will emit deleted (and possibly dirty for the dir, if mtime changed) 0400 KDirWatch watch; 0401 watch.addFile(file1); 0402 0403 KDirWatch::statistics(); 0404 0405 QSignalSpy spyDirty(&watch, &KDirWatch::dirty); 0406 QFile::remove(file1); 0407 QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), file1)); 0408 QTest::qWait(40); // just in case delayed processing would emit it 0409 QCOMPARE(spyDirty.count(), 0); 0410 } 0411 0412 void KDirWatch_UnitTest::testDeleteAndRecreateFile() // Useful for /etc/localtime for instance 0413 { 0414 const QString subdir = m_path + QLatin1String("subdir"); 0415 QDir().mkdir(subdir); 0416 const QString file1 = subdir + QLatin1String("/1"); 0417 if (!QFile::exists(file1)) { 0418 createFile(file1); 0419 } 0420 waitUntilMTimeChange(file1); 0421 0422 // Watch the file, then delete it, KDirWatch will emit deleted (and possibly dirty for the dir, if mtime changed) 0423 KDirWatch watch; 0424 watch.addFile(file1); 0425 0426 // Make sure this even works multiple times, as needed for ksycoca 0427 for (int i = 0; i < 5; ++i) { 0428 if (m_slow || watch.internalMethod() == KDirWatch::QFSWatch) { 0429 waitUntilNewSecond(); 0430 } 0431 0432 qCDebug(KCOREADDONS_DEBUG) << "Attempt #" << (i + 1) << "removing+recreating" << file1; 0433 0434 // When watching for a deleted + created signal pair, the two might come so close that 0435 // using waitForOneSignal will miss the created signal. This function monitors both all 0436 // the time to ensure both are received. 0437 // 0438 // In addition, allow dirty() to be emitted (for that same path) as an alternative 0439 0440 const QString expectedPath = file1; 0441 QSignalSpy spyDirty(&watch, &KDirWatch::dirty); 0442 QSignalSpy spyDeleted(&watch, &KDirWatch::deleted); 0443 QSignalSpy spyCreated(&watch, &KDirWatch::created); 0444 0445 // WHEN 0446 QFile::remove(file1); 0447 // And recreate immediately, to try and fool KDirWatch with unchanged ctime/mtime ;) 0448 // (This emulates the /etc/localtime case) 0449 createFile(file1); 0450 0451 // THEN 0452 int numTries = 0; 0453 while (spyDeleted.isEmpty() && spyDirty.isEmpty()) { 0454 if (++numTries > s_maxTries) { 0455 QFAIL("Failed to detect file deletion and recreation through either a deleted/created signal pair or through a dirty signal!"); 0456 return; 0457 } 0458 spyDeleted.wait(50); 0459 while (!spyDirty.isEmpty()) { 0460 if (spyDirty.at(0).at(0).toString() != expectedPath) { // unrelated 0461 spyDirty.removeFirst(); 0462 } else { 0463 break; 0464 } 0465 } 0466 } 0467 if (!spyDirty.isEmpty()) { 0468 continue; // all ok 0469 } 0470 0471 // Don't bother waiting for the created signal if the signal spy already received a signal. 0472 if (spyCreated.isEmpty() && !spyCreated.wait(50 * s_maxTries)) { 0473 qWarning() << "Timeout waiting for KDirWatch signal created(QString) (" << expectedPath << ")"; 0474 QFAIL("Timeout waiting for KDirWatch signal created, after deleted was emitted"); 0475 return; 0476 } 0477 0478 QVERIFY(verifySignalPath(spyDeleted, "deleted(QString)", expectedPath) && verifySignalPath(spyCreated, "created(QString)", expectedPath)); 0479 } 0480 0481 waitUntilMTimeChange(file1); 0482 0483 appendToFile(file1); 0484 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1)); 0485 } 0486 0487 void KDirWatch_UnitTest::testDeleteAndRecreateDir() 0488 { 0489 // Like KDirModelTest::testOverwriteFileWithDir does at the end. 0490 // The linux-2.6.31 bug made kdirwatch emit deletion signals about the -new- dir! 0491 QTemporaryDir *tempDir1 = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + QLatin1String("olddir-")); 0492 KDirWatch watch; 0493 const QString path1 = tempDir1->path() + QLatin1Char('/'); 0494 watch.addDir(path1); 0495 0496 delete tempDir1; 0497 QTemporaryDir *tempDir2 = new QTemporaryDir(QDir::tempPath() + QLatin1Char('/') + QLatin1String("newdir-")); 0498 const QString path2 = tempDir2->path() + QLatin1Char('/'); 0499 watch.addDir(path2); 0500 0501 QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), path1)); 0502 0503 delete tempDir2; 0504 } 0505 0506 void KDirWatch_UnitTest::testMoveTo() 0507 { 0508 // This reproduces the famous digikam crash, #222974 0509 // A watched file was being rewritten (overwritten by ksavefile), 0510 // which gives inotify notifications "moved_to" followed by "delete_self" 0511 // 0512 // What happened then was that the delayed slotRescan 0513 // would adjust things, making it status==Normal but the entry was 0514 // listed as a "non-existent sub-entry" for the parent directory. 0515 // That's inconsistent, and after removeFile() a dangling sub-entry would be left. 0516 0517 // Initial data: creating file subdir/1 0518 const QString file1 = m_path + QLatin1String("moveTo"); 0519 createFile(file1); 0520 0521 KDirWatch watch; 0522 watch.addDir(m_path); 0523 watch.addFile(file1); 0524 watch.startScan(); 0525 0526 if (watch.internalMethod() != KDirWatch::INotify) { 0527 waitUntilMTimeChange(m_path); 0528 } 0529 0530 // Atomic rename of "temp" to "file1", much like KAutoSave would do when saving file1 again 0531 // ### TODO: this isn't an atomic rename anymore. We need ::rename for that, or API from Qt. 0532 const QString filetemp = m_path + QLatin1String("temp"); 0533 createFile(filetemp); 0534 QFile::remove(file1); 0535 QVERIFY(QFile::rename(filetemp, file1)); // overwrite file1 with the tempfile 0536 qCDebug(KCOREADDONS_DEBUG) << "Overwrite file1 with tempfile"; 0537 0538 QSignalSpy spyCreated(&watch, &KDirWatch::created); 0539 QSignalSpy spyDirty(&watch, &KDirWatch::dirty); 0540 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); 0541 0542 // Getting created() on an unwatched file is an inotify bonus, it's not part of the requirements. 0543 if (watch.internalMethod() == KDirWatch::INotify) { 0544 QCOMPARE(spyCreated.count(), 1); 0545 QCOMPARE(spyCreated[0][0].toString(), file1); 0546 0547 QCOMPARE(spyDirty.size(), 2); 0548 QCOMPARE(spyDirty[1][0].toString(), filetemp); 0549 } 0550 0551 // make sure we're still watching it 0552 appendToFile(file1); 0553 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1)); 0554 0555 watch.removeFile(file1); // now we remove it 0556 0557 // Just touch another file to trigger a findSubEntry - this where the crash happened 0558 waitUntilMTimeChange(m_path); 0559 createFile(filetemp); 0560 #ifdef Q_OS_WIN 0561 if (watch.internalMethod() == KDirWatch::QFSWatch) { 0562 QEXPECT_FAIL(nullptr, "QFSWatch fails here on Windows!", Continue); 0563 } 0564 #endif 0565 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); 0566 } 0567 0568 void KDirWatch_UnitTest::nestedEventLoop() // #220153: watch two files, and modify 2nd while in slot for 1st 0569 { 0570 KDirWatch watch; 0571 0572 const QString file0 = m_path + QLatin1String("nested_0"); 0573 watch.addFile(file0); 0574 const QString file1 = m_path + QLatin1String("nested_1"); 0575 watch.addFile(file1); 0576 watch.startScan(); 0577 0578 if (m_slow) { 0579 waitUntilNewSecond(); 0580 } 0581 0582 appendToFile(file0); 0583 0584 // use own spy, to connect it before nestedEventLoopSlot, otherwise it reverses order 0585 QSignalSpy spyDirty(&watch, &KDirWatch::dirty); 0586 connect(&watch, &KDirWatch::dirty, this, &KDirWatch_UnitTest::nestedEventLoopSlot); 0587 waitForDirtySignal(watch, 1); 0588 QVERIFY(spyDirty.count() >= 2); 0589 QCOMPARE(spyDirty[0][0].toString(), file0); 0590 QCOMPARE(spyDirty[spyDirty.count() - 1][0].toString(), file1); 0591 } 0592 0593 void KDirWatch_UnitTest::nestedEventLoopSlot() 0594 { 0595 const KDirWatch *const_watch = qobject_cast<const KDirWatch *>(sender()); 0596 KDirWatch *watch = const_cast<KDirWatch *>(const_watch); 0597 // let's not come in this slot again 0598 disconnect(watch, &KDirWatch::dirty, this, &KDirWatch_UnitTest::nestedEventLoopSlot); 0599 0600 const QString file1 = m_path + QLatin1String("nested_1"); 0601 appendToFile(file1); 0602 // The nested event processing here was from a messagebox in #220153 0603 QList<QVariantList> spy = waitForDirtySignal(*watch, 1); 0604 QVERIFY(spy.count() >= 1); 0605 QCOMPARE(spy[spy.count() - 1][0].toString(), file1); 0606 0607 // Now the user pressed reload... 0608 const QString file0 = m_path + QLatin1String("nested_0"); 0609 watch->removeFile(file0); 0610 watch->addFile(file0); 0611 } 0612 0613 void KDirWatch_UnitTest::testHardlinkChange() 0614 { 0615 #ifdef Q_OS_UNIX 0616 0617 // The unittest for the "detecting hardlink change to /etc/localtime" problem 0618 // described on kde-core-devel (2009-07-03). 0619 // It shows that watching a specific file doesn't inform us that the file is 0620 // being recreated. Better watch the directory, for that. 0621 // Well, it works with inotify (and fam - which uses inotify I guess?) 0622 0623 const QString existingFile = m_path + QLatin1String("ExistingFile"); 0624 KDirWatch watch; 0625 watch.addFile(existingFile); 0626 watch.startScan(); 0627 0628 QFile::remove(existingFile); 0629 const QString testFile = m_path + QLatin1String("TestFile"); 0630 QVERIFY(::link(QFile::encodeName(testFile).constData(), QFile::encodeName(existingFile).constData()) == 0); // make ExistingFile "point" to TestFile 0631 QVERIFY(QFile::exists(existingFile)); 0632 0633 QVERIFY(waitForRecreationSignal(watch, existingFile)); 0634 0635 // The mtime of the existing file is the one of "TestFile", so it's old. 0636 // We won't detect the change then, if we use that as baseline for waiting. 0637 // We really need msec granularity, but that requires using statx which isn't available everywhere... 0638 waitUntilNewSecond(); 0639 appendToFile(existingFile); 0640 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), existingFile)); 0641 #else 0642 QSKIP("Unix-specific"); 0643 #endif 0644 } 0645 0646 void KDirWatch_UnitTest::stopAndRestart() 0647 { 0648 KDirWatch watch; 0649 watch.addDir(m_path); 0650 watch.startScan(); 0651 0652 waitUntilMTimeChange(m_path); 0653 0654 watch.stopDirScan(m_path); 0655 0656 qCDebug(KCOREADDONS_DEBUG) << "create file 2 at" << QDateTime::currentDateTime().toMSecsSinceEpoch(); 0657 const QString file2 = createFile(2); 0658 QSignalSpy spyDirty(&watch, &KDirWatch::dirty); 0659 QTest::qWait(200); 0660 QCOMPARE(spyDirty.count(), 0); // suspended -> no signal 0661 0662 watch.restartDirScan(m_path); 0663 0664 QTest::qWait(200); 0665 0666 #ifndef Q_OS_WIN 0667 QCOMPARE(spyDirty.count(), 0); // as documented by restartDirScan: no signal 0668 // On Windows, however, signals will get emitted, due to the ifdef Q_OS_WIN in the timestamp 0669 // comparison ("trust QFSW since the mtime of dirs isn't modified") 0670 #endif 0671 0672 KDirWatch::statistics(); 0673 0674 waitUntilMTimeChange(m_path); // necessary for the mtime comparison in scanEntry 0675 0676 qCDebug(KCOREADDONS_DEBUG) << "create file 3 at" << QDateTime::currentDateTime().toMSecsSinceEpoch(); 0677 const QString file3 = createFile(3); 0678 #ifdef Q_OS_WIN 0679 if (watch.internalMethod() == KDirWatch::QFSWatch) { 0680 QEXPECT_FAIL(nullptr, "QFSWatch fails here on Windows!", Continue); 0681 } 0682 #endif 0683 QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path)); 0684 0685 QFile::remove(file2); 0686 QFile::remove(file3); 0687 } 0688 0689 void KDirWatch_UnitTest::testRefcounting() 0690 { 0691 #if QT_CONFIG(cxx11_future) 0692 bool initialExists = false; 0693 bool secondExists = true; // the expectation is it will be set false 0694 auto thread = QThread::create([&] { 0695 QTemporaryDir dir; 0696 { 0697 KDirWatch watch; 0698 watch.addFile(dir.path()); 0699 initialExists = KDirWatch::exists(); 0700 } // out of scope, the internal private should have been unset 0701 secondExists = KDirWatch::exists(); 0702 }); 0703 thread->start(); 0704 thread->wait(); 0705 delete thread; 0706 QVERIFY(initialExists); 0707 QVERIFY(!secondExists); 0708 #endif 0709 } 0710 0711 #include "kdirwatch_unittest.moc"