~ubuntu-branches/ubuntu/natty/kde4libs/natty-proposed

« back to all changes in this revision

Viewing changes to kdecore/tests/kdirwatch_unittest.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Riddell, Scott Kitterman, Jonathan Riddell
  • Date: 2010-11-22 17:59:02 UTC
  • mfrom: (1.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20101122175902-yubxubd0pg6hn11z
Tags: 4:4.5.80a-0ubuntu1
[ Scott Kitterman ]
* New upstream beta release
  - Refreshed all patches
  - Updated debian/patches/10_make_libkdeinit4_private.diff to use Qfile
    instead of Qstring in kdecore/kernel/kstandarddirs_unix.cpp
  - Updated debian/patches/kubuntu_01_kubuntu_useragent.diff to provide
    Kubuntu in the Konqueror user agen string with the changes in
    kio/kio/kprotocolmanager.cpp
  - Partially updated debian/patches/kubuntu_05_langpack_desktop_files.diff
    and left the balance in kdecore/localization/klocale.cpp.rej for later
    revision
  - Update debian/patches/kubuntu_06_user_disk_mounting.diff for changes in
    solid/solid/backends/hal/halstorageaccess.cpp
  - Remove debian/patches/kubuntu_71_backport_plasma_webview_changes.diff
    (backported from upstream, so already present now)
  - Add minimum version for libattica-dev of 0.1.90 to build-depends
  - Bump minimum version for libsoprano-dev build-depend to 2.5.60
  - Add minimum version for shared-desktop-ontologies of 0.5 in build-dep

[ Jonathan Riddell ]
* Add build-depends on grantlee, libudev-dev, hupnp (FIXME needs packaging fixes)
* Update kubuntu_04_add_langpack_path.diff 28_find_old_kde4_html_documentation.diff
  22_hack_in_etc_kde4_in_kstandarddirs.diff for QT_NO_CAST_FROM_ASCII
* Update kubuntu_05_langpack_desktop_files.diff for new upstream code
* Add kubuntu_78_solid_trunk.diff to fix solid linking
* Add libnepomukutils4 package for new library
* Don't install kcm_ssl for now, e-mailed upstream to suggest moving to kdebase
* Add kubuntu_79_knewstuff_fix.diff to fix compile broken by non-trunk commit
  http://websvn.kde.org/?view=revision&revision=1199825
* kdelibs5-data replaces old kdebase-runtime-data due to moved file
* Add kubuntu_80_find_hupnp.diff to find hupnp include files, committed upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
    Boston, MA 02110-1301, USA.
19
19
*/
20
20
 
 
21
#include <kstandarddirs.h>
21
22
#include <kconfiggroup.h>
22
23
#include <QDir>
23
24
#include <kdebug.h>
46
47
 
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);
 
53
        } else {
 
54
            config.deleteEntry("PreferredMethod");
 
55
        }
49
56
        config.writeEntry("PollInterval", 50);
 
57
 
 
58
        KDirWatch foo;
 
59
        m_slow = (foo.internalMethod() == KDirWatch::FAM || foo.internalMethod() == KDirWatch::Stat);
50
60
    }
51
61
 
52
62
private Q_SLOTS: // test methods
63
73
    void removeAndReAdd();
64
74
    void watchNonExistent();
65
75
    void testDelete();
66
 
    void testDeleteAndRecreate();
 
76
    void testDeleteAndRecreateFile();
 
77
    void testDeleteAndRecreateDir();
67
78
    void testMoveTo();
68
79
    void nestedEventLoop();
69
80
    void testHardlinkChange();
73
84
 
74
85
private:
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);
84
98
 
85
99
    KTempDir m_tempDir;
86
100
    QString m_path;
 
101
    bool m_slow;
87
102
};
88
103
 
89
104
QTEST_KDEMAIN_CORE(KDirWatch_UnitTest)
104
119
    //kDebug() << path;
105
120
}
106
121
 
107
 
// helper method: create a file (identifier by number)
 
122
// helper method: create a file (identified by number)
108
123
QString KDirWatch_UnitTest::createFile(int num)
109
124
{
110
125
    const QString fileName = s_filePrefix + QString::number(num);
112
127
    return m_path + fileName;
113
128
}
114
129
 
 
130
// helper method: delete a file (identified by number)
 
131
void KDirWatch_UnitTest::removeFile(int num)
 
132
{
 
133
    const QString fileName = s_filePrefix + QString::number(num);
 
134
    QFile::remove(m_path + fileName);
 
135
}
 
136
 
115
137
static QByteArray printableTime(time_t mtime)
116
138
{
117
139
    struct tm* tmp = localtime(&mtime);
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);
 
154
}
 
155
 
 
156
void KDirWatch_UnitTest::waitUntilNewSecond()
 
157
{
 
158
    struct timeval now_tv;
 
159
    gettimeofday(&now_tv, NULL);
 
160
    waitUntilAfter(now_tv.tv_sec);
 
161
}
 
162
 
 
163
void KDirWatch_UnitTest::waitUntilAfter(time_t ctime)
 
164
{
130
165
    int totalWait = 0;
131
166
    struct timeval now_tv;
132
 
    time_t ctime;
133
 
 
134
167
    Q_FOREVER {
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.
139
 
#ifdef Q_OS_WIN
140
 
        // ctime is the 'creation time' on windows - use mtime instead
141
 
        ctime = stat_buf.st_mtime;
142
 
#else
143
 
        ctime = stat_buf.st_ctime;
144
 
#endif
145
172
 
146
173
        if (now_tv.tv_sec == ctime) {
147
174
            totalWait += 50;
148
175
            QTest::qWait(50);
149
176
        } else {
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
151
179
            break;
152
180
        }
153
181
    }
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);
156
184
}
157
185
 
158
186
// helper method: modifies a file
163
191
    //const QString directory = QDir::cleanPath(path+"/..");
164
192
    //waitUntilMTimeChange(directory);
165
193
 
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);
171
 
 
172
194
    QFile file(path);
173
195
    QVERIFY(file.open(QIODevice::Append | QIODevice::WriteOnly));
174
196
    file.write(QByteArray("foobar"));
175
197
    file.close();
 
198
 
 
199
    if (0) {
 
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);
 
205
    }
176
206
}
177
207
 
178
208
// helper method: modifies a file (identified by number)
182
212
    appendToFile(m_path + fileName);
183
213
}
184
214
 
 
215
static QString removeTrailingSlash(const QString& path)
 
216
{
 
217
    if (path.endsWith('/')) {
 
218
        return path.left(path.length()-1);
 
219
    } else {
 
220
        return path;
 
221
    }
 
222
}
 
223
 
185
224
// helper method
186
225
QList<QVariantList> KDirWatch_UnitTest::waitForDirtySignal(KDirWatch& watch, int expected)
187
226
{
200
239
 
201
240
bool KDirWatch_UnitTest::waitForOneSignal(KDirWatch& watch, const char* sig, const QString& path)
202
241
{
203
 
    QSignalSpy spyDirty(&watch, sig);
204
 
    int numTries = 0;
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 << ")";
209
 
            return false;
210
 
        }
211
 
        QTest::qWait(50);
 
242
    const QString expectedPath = removeTrailingSlash(path);
 
243
    while (true) {
 
244
        QSignalSpy spyDirty(&watch, sig);
 
245
        int numTries = 0;
 
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 << ")";
 
250
                return false;
 
251
            }
 
252
            QTest::qWait(50);
 
253
        }
 
254
        const QString got = spyDirty[0][0].toString();
 
255
        if (got == expectedPath)
 
256
            return true;
 
257
        if (got.startsWith(expectedPath + '/')) {
 
258
            kDebug() << "Ignoring (inotify) notification of" << sig << '(' << got << ')';
 
259
            continue;
 
260
        }
 
261
        kWarning() << "Expected" << sig << '(' << removeTrailingSlash(path) << ')' << "but got" << sig << '(' << got << ')';
 
262
        return false;
212
263
    }
213
 
    return true;
214
264
}
215
265
 
216
266
QList<QVariantList> KDirWatch_UnitTest::waitForCreatedSignal(KDirWatch& watch, int expected)
251
301
 
252
302
    waitUntilMTimeChange(m_path);
253
303
 
 
304
    // dirty(the directory) should be emitted.
254
305
    const QString file0 = createFile(0);
255
 
    QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file0));
 
306
    QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
 
307
 
 
308
    removeFile(0);
256
309
}
257
310
 
258
311
void KDirWatch_UnitTest::touch1000Files()
261
314
    watch.addDir(m_path);
262
315
    watch.startScan();
263
316
 
264
 
    const int fileCount = 1000;
 
317
    waitUntilMTimeChange(m_path);
 
318
 
 
319
    const int fileCount = 100;
265
320
    for (int i = 0; i < fileCount; ++i) {
266
321
        createFile(i);
267
322
    }
268
323
 
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();
 
328
    } else {
 
329
        // More stupid backends just see one mtime change on the directory
 
330
        QVERIFY(spy.count() >= 1);
 
331
    }
 
332
 
 
333
    for (int i = 0; i < fileCount; ++i) {
 
334
        removeFile(i);
 
335
    }
272
336
}
273
337
 
274
338
void KDirWatch_UnitTest::watchAndModifyOneFile() // watch a specific file, and modify it
277
341
    const QString existingFile = m_path + "ExistingFile";
278
342
    watch.addFile(existingFile);
279
343
    watch.startScan();
 
344
    if (m_slow)
 
345
        waitUntilNewSecond();
280
346
    appendToFile(existingFile);
281
347
    QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), existingFile));
282
348
}
286
352
    KDirWatch watch;
287
353
    watch.addDir(m_path);
288
354
    watch.startScan();
289
 
    createFile(0);
290
 
    QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path + s_filePrefix + "0"));
 
355
    if (m_slow)
 
356
        waitUntilNewSecond(); // necessary for FAM
 
357
    const QString file0 = createFile(0);
 
358
    QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
291
359
 
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));
297
368
}
298
369
 
299
370
void KDirWatch_UnitTest::watchNonExistent()
305
376
    watch.addDir(subdir);
306
377
    watch.startScan();
307
378
 
 
379
    if (m_slow)
 
380
        waitUntilNewSecond();
 
381
 
308
382
    // Now create it, KDirWatch should emit created()
 
383
    kDebug() << "Creating" << subdir;
309
384
    QDir().mkdir(subdir);
310
385
 
311
386
    QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), subdir));
312
387
 
 
388
    KDirWatch::statistics();
 
389
 
313
390
    // Play with addDir/removeDir, just for fun
314
391
    watch.addDir(subdir);
315
392
    watch.removeDir(subdir);
322
399
    watch.addFile(file1); // doesn't exist yet
323
400
    watch.removeFile(file1); // forget it again
324
401
 
 
402
    KDirWatch::statistics();
 
403
 
 
404
    QVERIFY(!QFile::exists(file));
325
405
    // Now create it, KDirWatch should emit created
 
406
    kDebug() << "Creating" << file;
326
407
    createFile(file);
327
408
    QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), file));
328
409
 
354
435
    QCOMPARE(spyDirty.count(), 0);
355
436
}
356
437
 
357
 
void KDirWatch_UnitTest::testDeleteAndRecreate()
 
438
void KDirWatch_UnitTest::testDeleteAndRecreateFile() // Useful for /etc/localtime for instance
358
439
{
359
440
    const QString subdir = m_path + "subdir";
360
441
    QDir().mkdir(subdir);
367
448
    KDirWatch watch;
368
449
    watch.addFile(file1);
369
450
 
 
451
    //KDE_struct_stat stat_buf;
 
452
    //QCOMPARE(KDE::stat(QFile::encodeName(file1), &stat_buf), 0);
 
453
    //kDebug() << "initial inode" << stat_buf.st_ino;
 
454
 
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));
 
459
 
 
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);
 
463
    }
 
464
    //QCOMPARE(KDE::stat(QFile::encodeName(file1), &stat_buf), 0);
 
465
    //kDebug() << "new inode" << stat_buf.st_ino; // same!
 
466
 
 
467
    if (watch.internalMethod() == KDirWatch::INotify) {
 
468
        QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), file1));
 
469
        QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), file1));
 
470
    } else {
 
471
        QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
 
472
    }
 
473
 
 
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);
 
478
    }
376
479
 
377
480
    waitUntilMTimeChange(file1);
378
 
    //KDirWatch::statistics();
379
481
 
380
482
    appendToFile(file1);
381
483
    QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
382
484
}
383
485
 
 
486
void KDirWatch_UnitTest::testDeleteAndRecreateDir()
 
487
{
 
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-"));
 
491
    KDirWatch watch;
 
492
    const QString path1 = tempDir1->name();
 
493
    watch.addDir(path1);
 
494
 
 
495
    delete tempDir1;
 
496
    KTempDir* tempDir2 = new KTempDir(KStandardDirs::locateLocal("tmp", "newdir-"));
 
497
    const QString path2 = tempDir2->name();
 
498
    watch.addDir(path2);
 
499
 
 
500
    QVERIFY(waitForOneSignal(watch, SIGNAL(deleted(QString)), path1));
 
501
 
 
502
    delete tempDir2;
 
503
}
 
504
 
 
505
 
384
506
void KDirWatch_UnitTest::testMoveTo()
385
507
{
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.
390
511
    //
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();
404
525
 
 
526
    if (watch.internalMethod() != KDirWatch::INotify)
 
527
        waitUntilMTimeChange(m_path);
 
528
 
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
409
 
 
410
 
    QVERIFY(waitForOneSignal(watch, SIGNAL(created(QString)), file1));
 
533
    kDebug() << "Overwrite file1 with tempfile";
 
534
 
 
535
    QSignalSpy spyCreated(&watch, SIGNAL(created(QString)));
 
536
    QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), m_path));
 
537
 
 
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);
 
542
    }
411
543
 
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));
 
545
    appendToFile(file1);
 
546
    QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), file1));
416
547
 
417
548
    //kDebug() << "after created";
418
549
    //KDirWatch::statistics();
436
567
    watch.addFile(file1);
437
568
    watch.startScan();
438
569
 
 
570
    if (m_slow)
 
571
        waitUntilNewSecond();
 
572
 
439
573
    appendToFile(file0);
440
574
 
441
575
    // use own spy, to connect it before nestedEventLoopSlot, otherwise it reverses order
477
611
    // described on kde-core-devel (2009-07-03).
478
612
    // It shows that watching a specific file doesn't inform us that the file is
479
613
    // being recreated. Better watch the directory, for that.
 
614
    // Well, it works with inotify (and fam - which uses inotify I guess?)
480
615
 
481
616
    const QString existingFile = m_path + "ExistingFile";
482
617
    KDirWatch watch;
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
 
627
 
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));
 
632
    else
 
633
        QVERIFY(waitForOneSignal(watch, SIGNAL(dirty(QString)), existingFile));
495
634
 
496
635
    //KDirWatch::statistics();
497
636