47
48
// Speed up the test by making the kdirwatch timer (to compress changes) faster
48
49
KConfigGroup config(KGlobal::config(), "DirWatch");
50
const QByteArray testMethod = qgetenv("KDIRWATCHTEST_METHOD");
51
if (!testMethod.isEmpty()) {
52
config.writeEntry("PreferredMethod", testMethod);
54
config.deleteEntry("PreferredMethod");
49
56
config.writeEntry("PollInterval", 50);
59
m_slow = (foo.internalMethod() == KDirWatch::FAM || foo.internalMethod() == KDirWatch::Stat);
52
62
private Q_SLOTS: // test methods
75
86
void waitUntilMTimeChange(const QString& path);
87
void waitUntilNewSecond();
88
void waitUntilAfter(time_t ctime);
76
89
QList<QVariantList> waitForDirtySignal(KDirWatch& watch, int expected);
77
90
QList<QVariantList> waitForCreatedSignal(KDirWatch& watch, int expected);
78
91
QList<QVariantList> waitForDeletedSignal(KDirWatch& watch, int expected);
79
92
bool waitForOneSignal(KDirWatch& watch, const char* sig, const QString& path);
80
93
void createFile(const QString& path);
81
94
QString createFile(int num);
95
void removeFile(int num);
82
96
void appendToFile(const QString& path);
83
97
void appendToFile(int num);
85
99
KTempDir m_tempDir;
89
104
QTEST_KDEMAIN_CORE(KDirWatch_UnitTest)
127
149
// otherwise this change will go unnoticed
128
150
KDE_struct_stat stat_buf;
129
151
QCOMPARE(KDE::stat(path, &stat_buf), 0);
152
const time_t ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
153
waitUntilAfter(ctime);
156
void KDirWatch_UnitTest::waitUntilNewSecond()
158
struct timeval now_tv;
159
gettimeofday(&now_tv, NULL);
160
waitUntilAfter(now_tv.tv_sec);
163
void KDirWatch_UnitTest::waitUntilAfter(time_t ctime)
130
165
int totalWait = 0;
131
166
struct timeval now_tv;
135
168
gettimeofday(&now_tv, NULL);
136
169
// The mtime only has a granularity of a second, that's the whole issue;
137
170
// We can't just QTest::qWait(now_tv.tv_sec - stat_buf.st_ctime), that would
138
171
// be a full second every time.
140
// ctime is the 'creation time' on windows - use mtime instead
141
ctime = stat_buf.st_mtime;
143
ctime = stat_buf.st_ctime;
146
173
if (now_tv.tv_sec == ctime) {
148
175
QTest::qWait(50);
150
Q_ASSERT(now_tv.tv_sec > ctime); // can't go back in time ;)
177
QVERIFY(now_tv.tv_sec > ctime); // can't go back in time ;)
178
QTest::qWait(50); // be safe
154
182
//if (totalWait > 0)
155
kDebug() << "File has ctime" << printableTime(stat_buf.st_ctime) << ", so I waited" << totalWait << "ms, now is" << printableTime(now_tv.tv_sec);
183
kDebug() << "Waited" << totalWait << "ms so that now" << printableTime(now_tv.tv_sec) << "is >" << printableTime(ctime);
158
186
// helper method: modifies a file
163
191
//const QString directory = QDir::cleanPath(path+"/..");
164
192
//waitUntilMTimeChange(directory);
166
//KDE_struct_stat stat_buf;
167
//QCOMPARE(KDE::stat(path, &stat_buf), 0);
168
//kDebug() << "After append: file ctime=" << printableTime(stat_buf.st_ctime);
169
//QCOMPARE(KDE::stat(directory, &stat_buf), 0);
170
//kDebug() << "After append: directory mtime=" << printableTime(stat_buf.st_ctime);
172
194
QFile file(path);
173
195
QVERIFY(file.open(QIODevice::Append | QIODevice::WriteOnly));
174
196
file.write(QByteArray("foobar"));
200
KDE_struct_stat stat_buf;
201
QCOMPARE(KDE::stat(path, &stat_buf), 0);
202
kDebug() << "After append: file ctime=" << printableTime(stat_buf.st_ctime);
203
QCOMPARE(KDE::stat(path, &stat_buf), 0);
204
kDebug() << "After append: directory mtime=" << printableTime(stat_buf.st_ctime);
178
208
// helper method: modifies a file (identified by number)
201
240
bool KDirWatch_UnitTest::waitForOneSignal(KDirWatch& watch, const char* sig, const QString& path)
203
QSignalSpy spyDirty(&watch, sig);
205
// Give time for KDirWatch to notify us
206
while (spyDirty.isEmpty()) {
207
if (++numTries > s_maxTries) {
208
kWarning() << "Timeout waiting for KDirWatch signal" << QByteArray(sig).mid(1) << "(" << path << ")";
242
const QString expectedPath = removeTrailingSlash(path);
244
QSignalSpy spyDirty(&watch, sig);
246
// Give time for KDirWatch to notify us
247
while (spyDirty.isEmpty()) {
248
if (++numTries > s_maxTries) {
249
kWarning() << "Timeout waiting for KDirWatch signal" << QByteArray(sig).mid(1) << "(" << path << ")";
254
const QString got = spyDirty[0][0].toString();
255
if (got == expectedPath)
257
if (got.startsWith(expectedPath + '/')) {
258
kDebug() << "Ignoring (inotify) notification of" << sig << '(' << got << ')';
261
kWarning() << "Expected" << sig << '(' << removeTrailingSlash(path) << ')' << "but got" << sig << '(' << got << ')';
216
266
QList<QVariantList> KDirWatch_UnitTest::waitForCreatedSignal(KDirWatch& watch, int expected)
261
314
watch.addDir(m_path);
262
315
watch.startScan();
264
const int fileCount = 1000;
317
waitUntilMTimeChange(m_path);
319
const int fileCount = 100;
265
320
for (int i = 0; i < fileCount; ++i) {
269
324
QList<QVariantList> spy = waitForDirtySignal(watch, fileCount);
270
QVERIFY(spy.count() >= fileCount);
271
qDebug() << spy.count();
325
if (watch.internalMethod() == KDirWatch::INotify) {
326
QVERIFY(spy.count() >= fileCount);
327
qDebug() << spy.count();
329
// More stupid backends just see one mtime change on the directory
330
QVERIFY(spy.count() >= 1);
333
for (int i = 0; i < fileCount; ++i) {
274
338
void KDirWatch_UnitTest::watchAndModifyOneFile() // watch a specific file, and modify it
287
353
watch.addDir(m_path);
288
354
watch.startScan();
290
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path + s_filePrefix + "0"));
356
waitUntilNewSecond(); // necessary for FAM
357
const QString file0 = createFile(0);
358
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
292
360
// Just like KDirLister does: remove the watch, then re-add it.
293
361
watch.removeDir(m_path);
294
362
watch.addDir(m_path);
363
if (watch.internalMethod() != KDirWatch::INotify)
364
waitUntilMTimeChange(m_path); // necessary for FAM and QFSWatcher
295
365
const QString file1 = createFile(1);
296
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
366
//kDebug() << "created" << file1;
367
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
299
370
void KDirWatch_UnitTest::watchNonExistent()
368
449
watch.addFile(file1);
451
//KDE_struct_stat stat_buf;
452
//QCOMPARE(KDE::stat(QFile::encodeName(file1), &stat_buf), 0);
453
//kDebug() << "initial inode" << stat_buf.st_ino;
370
455
QFile::remove(file1);
371
456
// And recreate immediately, to try and fool KDirWatch with unchanged ctime/mtime ;)
457
// (This emulates the /etc/localtime case)
372
458
createFile(file1);
373
//QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), file1));
374
//QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), subdir));
375
QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), file1));
460
// gamin does not signal the change in this case; probably because it uses polling internally...
461
if (watch.internalMethod() == KDirWatch::FAM || watch.internalMethod() == KDirWatch::Stat) {
462
QSKIP("Deleting and recreating a file is not detected by FAM (at least with gamin) or Stat", SkipAll);
464
//QCOMPARE(KDE::stat(QFile::encodeName(file1), &stat_buf), 0);
465
//kDebug() << "new inode" << stat_buf.st_ino; // same!
467
if (watch.internalMethod() == KDirWatch::INotify) {
468
QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), file1));
469
QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), file1));
471
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
474
// QFileSystemWatcher, as documented, stops watching when the file is deleted
475
// so the appendToFile below will fail. Or further changes to /etc/localtime...
476
if (watch.internalMethod() == KDirWatch::QFSWatch) {
477
QSKIP("Limitation of QFSWatcher: it stops watching when deleting+recreating the file", SkipAll);
377
480
waitUntilMTimeChange(file1);
378
//KDirWatch::statistics();
380
482
appendToFile(file1);
381
483
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
486
void KDirWatch_UnitTest::testDeleteAndRecreateDir()
488
// Like KDirModelTest::testOverwriteFileWithDir does at the end.
489
// The linux-2.6.31 bug made kdirwatch emit deletion signals about the -new- dir!
490
KTempDir* tempDir1 = new KTempDir(KStandardDirs::locateLocal("tmp", "olddir-"));
492
const QString path1 = tempDir1->name();
496
KTempDir* tempDir2 = new KTempDir(KStandardDirs::locateLocal("tmp", "newdir-"));
497
const QString path2 = tempDir2->name();
500
QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), path1));
384
506
void KDirWatch_UnitTest::testMoveTo()
386
508
// This reproduces the famous digikam crash, #222974
387
509
// A watched file was being rewritten (overwritten by ksavefile),
388
510
// which gives inotify notifications "moved_to" followed by "delete_self"
389
// -> inotify bug, email sent to the author.
391
// What happened then was that the delayed slotRescan
512
// What happened then was that the delayed slotRescan
392
513
// would adjust things, making it status==Normal but the entry was
393
514
// listed as a "non-existent sub-entry" for the parent directory.
394
515
// That's inconsistent, and after removeFile() a dangling sub-entry would be left.
402
523
watch.addFile(file1);
403
524
watch.startScan();
526
if (watch.internalMethod() != KDirWatch::INotify)
527
waitUntilMTimeChange(m_path);
405
529
// Atomic rename of "temp" to "file1", much like KAutoSave would do when saving file1 again
406
530
const QString filetemp = m_path + "temp";
407
531
createFile(filetemp);
408
532
QVERIFY(KDE::rename(filetemp, file1) == 0); // overwrite file1 with the tempfile
410
QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), file1));
533
kDebug() << "Overwrite file1 with tempfile";
535
QSignalSpy spyCreated(&watch, SIGNAL(created(QString)));
536
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
538
// Getting created() on an unwatched file is an inotify bonus, it's not part of the requirements.
539
if (watch.internalMethod() == KDirWatch::INotify) {
540
QCOMPARE(spyCreated.count(), 1);
541
QCOMPARE(spyCreated[0][0].toString(), file1);
412
544
// make sure we're still watching it
413
// ## this doesn't work, change is not detected, must be related to the inotify bug on overwrite
414
//appendToFile(file1);
415
//QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
546
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
417
548
//kDebug() << "after created";
418
549
//KDirWatch::statistics();
489
624
QFile::remove(existingFile);
490
625
const QString testFile = m_path + "TestFile";
491
626
::link(QFile::encodeName(testFile), QFile::encodeName(existingFile)); // make ExistingFile "point" to TestFile
492
628
QVERIFY(QFile::exists(existingFile));
493
629
//QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), existingFile));
494
QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), existingFile));
630
if (watch.internalMethod() == KDirWatch::INotify || watch.internalMethod() == KDirWatch::FAM)
631
QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), existingFile));
633
QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), existingFile));
496
635
//KDirWatch::statistics();