tests/auto/qfilesystemwatcher/tst_qfilesystemwatcher.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the test suite of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 #include <QtTest/QtTest>
       
    42 
       
    43 #include <QCoreApplication>
       
    44 
       
    45 #include <QFileSystemWatcher>
       
    46 
       
    47 #ifdef Q_OS_LINUX
       
    48 # ifdef QT_NO_INOTIFY
       
    49 #  include <linux/version.h>
       
    50 # else
       
    51 #  include <sys/inotify.h>
       
    52 # endif
       
    53 #endif
       
    54 
       
    55 //TESTED_CLASS=
       
    56 //TESTED_FILES=
       
    57 
       
    58 class tst_QFileSystemWatcher : public QObject
       
    59 {
       
    60     Q_OBJECT
       
    61 
       
    62 public:
       
    63     tst_QFileSystemWatcher();
       
    64 
       
    65 private slots:
       
    66     void basicTest_data();
       
    67     void basicTest();
       
    68 
       
    69     void watchDirectory_data() { basicTest_data(); }
       
    70     void watchDirectory();
       
    71 
       
    72     void addPath();
       
    73     void removePath();
       
    74     void addPaths();
       
    75     void removePaths();
       
    76 
       
    77     void watchFileAndItsDirectory_data() { basicTest_data(); }
       
    78     void watchFileAndItsDirectory();
       
    79 
       
    80     void nonExistingFile();
       
    81 
       
    82     void cleanup();
       
    83 private:
       
    84     QStringList do_force_engines;
       
    85     bool do_force_native;
       
    86 };
       
    87 
       
    88 tst_QFileSystemWatcher::tst_QFileSystemWatcher()
       
    89     : do_force_native(false)
       
    90 {
       
    91 #ifdef Q_OS_LINUX
       
    92     // the inotify implementation in the kernel is known to be buggy in certain versions of the linux kernel
       
    93     do_force_engines << "native";
       
    94     do_force_engines << "dnotify";
       
    95 
       
    96 #ifdef QT_NO_INOTIFY
       
    97     if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13))
       
    98         do_force_engines << "inotify";
       
    99 #else
       
   100     if (inotify_init() != -1)
       
   101         do_force_engines << "inotify";
       
   102 #endif
       
   103 #elif defined(Q_OS_WIN) || defined(Q_OS_DARWIN) || defined(Q_OS_FREEBSD) || defined(Q_OS_SYMBIAN)
       
   104     // we have native engines for win32, macosx and freebsd
       
   105     do_force_engines << "native";
       
   106 #endif
       
   107 }
       
   108 
       
   109 void tst_QFileSystemWatcher::basicTest_data()
       
   110 {
       
   111     QTest::addColumn<QString>("backend");
       
   112     foreach(QString engine, do_force_engines)
       
   113         QTest::newRow(engine.toLatin1().constData()) << engine;
       
   114     QTest::newRow("poller") << "poller";
       
   115 }
       
   116 
       
   117 void tst_QFileSystemWatcher::basicTest()
       
   118 {
       
   119     QFETCH(QString, backend);
       
   120     qDebug() << "Testing" << backend << "engine";
       
   121 
       
   122     // create test file
       
   123     QFile testFile("testfile.txt");
       
   124     testFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
       
   125     testFile.remove();
       
   126     QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate));
       
   127     testFile.write(QByteArray("hello"));
       
   128     testFile.close();
       
   129 
       
   130     // set some file permissions
       
   131     testFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
       
   132 
       
   133     // create watcher, forcing it to use a specific backend
       
   134     QFileSystemWatcher watcher;
       
   135     watcher.setObjectName(QLatin1String("_qt_autotest_force_engine_") + backend);
       
   136     watcher.addPath(testFile.fileName());
       
   137 
       
   138     QSignalSpy changedSpy(&watcher, SIGNAL(fileChanged(const QString &)));
       
   139     QEventLoop eventLoop;
       
   140     connect(&watcher,
       
   141             SIGNAL(fileChanged(const QString &)),
       
   142             &eventLoop,
       
   143             SLOT(quit()));
       
   144     QTimer timer;
       
   145     connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
       
   146 
       
   147     // modify the file, should get a signal from the watcher
       
   148 
       
   149     // resolution of the modification time is system dependent, but it's at most 1 second when using
       
   150     // the polling engine. I've heard rumors that FAT32 has a 2 second resolution. So, we have to
       
   151     // wait a bit before we can modify the file (hrmph)...
       
   152 #ifndef Q_OS_WINCE
       
   153     QTest::qWait(2000);
       
   154 #else
       
   155     // WinCE is always a little bit slower. Give it a little bit more time
       
   156     QTest::qWait(5000);
       
   157 #endif
       
   158 
       
   159     testFile.open(QIODevice::WriteOnly | QIODevice::Append);
       
   160     testFile.write(QByteArray("world"));
       
   161     testFile.close();
       
   162 
       
   163     // qDebug() << "waiting max 5 seconds for notification for file modification to trigger(1)";
       
   164     timer.start(5000);
       
   165     eventLoop.exec();
       
   166 
       
   167     QCOMPARE(changedSpy.count(), 1);
       
   168     QCOMPARE(changedSpy.at(0).count(), 1);
       
   169 
       
   170     QString fileName = changedSpy.at(0).at(0).toString();
       
   171     QCOMPARE(fileName, testFile.fileName());
       
   172 
       
   173     changedSpy.clear();
       
   174 
       
   175     // remove the watch and modify the file, should not get a signal from the watcher
       
   176     watcher.removePath(testFile.fileName());
       
   177     testFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
       
   178     testFile.write(QByteArray("hello universe!"));
       
   179     testFile.close();
       
   180 
       
   181     // qDebug() << "waiting max 5 seconds for notification for file modification to trigger (2)";
       
   182     timer.start(5000);
       
   183     eventLoop.exec();
       
   184 
       
   185     QCOMPARE(changedSpy.count(), 0);
       
   186 
       
   187     // readd the file watch with a relative path
       
   188     watcher.addPath(testFile.fileName().prepend("./"));
       
   189     testFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
       
   190     testFile.write(QByteArray("hello multiverse!"));
       
   191     testFile.close();
       
   192 
       
   193     timer.start(5000);
       
   194     eventLoop.exec();
       
   195 
       
   196     QVERIFY(changedSpy.count() > 0);
       
   197 
       
   198     watcher.removePath(testFile.fileName().prepend("./"));
       
   199 
       
   200     changedSpy.clear();
       
   201 
       
   202     // readd the file watch
       
   203     watcher.addPath(testFile.fileName());
       
   204 
       
   205     // change the permissions, should get a signal from the watcher
       
   206     testFile.setPermissions(QFile::ReadOwner);
       
   207 
       
   208     // qDebug() << "waiting max 5 seconds for notification for file permission modification to trigger(1)";
       
   209     timer.start(5000);
       
   210     eventLoop.exec();
       
   211 
       
   212     QCOMPARE(changedSpy.count(), 1);
       
   213     QCOMPARE(changedSpy.at(0).count(), 1);
       
   214 
       
   215     fileName = changedSpy.at(0).at(0).toString();
       
   216     QCOMPARE(fileName, testFile.fileName());
       
   217 
       
   218     changedSpy.clear();
       
   219 
       
   220     // remove the watch and modify file permissions, should not get a signal from the watcher
       
   221     watcher.removePath(testFile.fileName());
       
   222     testFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOther);
       
   223 
       
   224     // qDebug() << "waiting max 5 seconds for notification for file modification to trigger (2)";
       
   225     timer.start(5000);
       
   226     eventLoop.exec();
       
   227 
       
   228     QCOMPARE(changedSpy.count(), 0);
       
   229 
       
   230     // readd the file watch
       
   231     watcher.addPath(testFile.fileName());
       
   232 
       
   233     // remove the file, should get a signal from the watcher
       
   234     QVERIFY(testFile.remove());
       
   235 
       
   236     // qDebug() << "waiting max 5 seconds for notification for file removal to trigger";
       
   237     timer.start(5000);
       
   238     eventLoop.exec();
       
   239 
       
   240     QVERIFY(changedSpy.count() == 1 || changedSpy.count() == 2); // removing a file on some filesystems seems to deliver 2 notifications
       
   241     QCOMPARE(changedSpy.at(0).count(), 1);
       
   242 
       
   243     fileName = changedSpy.at(0).at(0).toString();
       
   244     QCOMPARE(fileName, testFile.fileName());
       
   245 
       
   246     changedSpy.clear();
       
   247 
       
   248     // recreate the file, we should not get any notification
       
   249     QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate));
       
   250     testFile.write(QByteArray("hello"));
       
   251     testFile.close();
       
   252 
       
   253     // qDebug() << "waiting max 5 seconds for notification for file recreation to trigger";
       
   254     timer.start(5000);
       
   255     eventLoop.exec();
       
   256 
       
   257     QCOMPARE(changedSpy.count(), 0);
       
   258 
       
   259     QVERIFY(testFile.remove());
       
   260 }
       
   261 
       
   262 void tst_QFileSystemWatcher::watchDirectory()
       
   263 {
       
   264     QFETCH(QString, backend);
       
   265     qDebug() << "Testing" << backend << "engine";
       
   266 
       
   267     QDir().mkdir("testDir");
       
   268     QDir testDir("testDir");
       
   269 
       
   270     QString testFileName = testDir.filePath("testFile.txt");
       
   271     QFile::remove(testFileName);
       
   272 
       
   273     QFileSystemWatcher watcher;
       
   274     watcher.setObjectName(QLatin1String("_qt_autotest_force_engine_") + backend);
       
   275     watcher.addPath(testDir.dirName());
       
   276 
       
   277     QSignalSpy changedSpy(&watcher, SIGNAL(directoryChanged(const QString &)));
       
   278     QEventLoop eventLoop;
       
   279     connect(&watcher,
       
   280             SIGNAL(directoryChanged(const QString &)),
       
   281             &eventLoop,
       
   282             SLOT(quit()));
       
   283     QTimer timer;
       
   284     connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
       
   285 
       
   286     // resolution of the modification time is system dependent, but it's at most 1 second when using
       
   287     // the polling engine. From what I know, FAT32 has a 2 second resolution. So we have to
       
   288     // wait before modifying the directory...
       
   289     QTest::qWait(2000);
       
   290     QFile testFile(testFileName);
       
   291     QString fileName;
       
   292 
       
   293     // remove the watch, should not get notification of a new file
       
   294     watcher.removePath(testDir.dirName());
       
   295     QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate));
       
   296     testFile.close();
       
   297 
       
   298     // qDebug() << "waiting max 5 seconds for notification for file recreationg to trigger";
       
   299     timer.start(5000);
       
   300     eventLoop.exec();
       
   301 
       
   302     QCOMPARE(changedSpy.count(), 0);
       
   303 
       
   304     watcher.addPath(testDir.dirName());
       
   305 
       
   306     // remove the file again, should get a signal from the watcher
       
   307     QVERIFY(testFile.remove());
       
   308 
       
   309     timer.start(5000);
       
   310     eventLoop.exec();
       
   311 
       
   312     // remove the directory, should get a signal from the watcher
       
   313     QVERIFY(QDir().rmdir("testDir"));
       
   314 
       
   315     // qDebug() << "waiting max 5 seconds for notification for directory removal to trigger";
       
   316     timer.start(5000);
       
   317     eventLoop.exec();
       
   318 
       
   319 #ifdef Q_OS_WINCE
       
   320     QEXPECT_FAIL("poller", "Directory does not get updated on file removal(See #137910)", Abort);
       
   321 #endif
       
   322     QCOMPARE(changedSpy.count(), 2);
       
   323     QCOMPARE(changedSpy.at(0).count(), 1);
       
   324     QCOMPARE(changedSpy.at(1).count(), 1);
       
   325 
       
   326     fileName = changedSpy.at(0).at(0).toString();
       
   327     QCOMPARE(fileName, testDir.dirName());
       
   328     fileName = changedSpy.at(1).at(0).toString();
       
   329     QCOMPARE(fileName, testDir.dirName());
       
   330 
       
   331     changedSpy.clear();
       
   332 
       
   333     // recreate the file, we should not get any notification
       
   334     if (!QDir().mkdir("testDir"))
       
   335         QSKIP("Failed to recreate directory, skipping final test.", SkipSingle);
       
   336 
       
   337     // qDebug() << "waiting max 5 seconds for notification for dir recreation to trigger";
       
   338     timer.start(5000);
       
   339     eventLoop.exec();
       
   340 
       
   341     QCOMPARE(changedSpy.count(), 0);
       
   342 
       
   343     QVERIFY(QDir().rmdir("testDir"));
       
   344 }
       
   345 
       
   346 void tst_QFileSystemWatcher::addPath()
       
   347 {
       
   348     QFileSystemWatcher watcher;
       
   349     QString home = QDir::homePath();
       
   350     watcher.addPath(home);
       
   351     QCOMPARE(watcher.directories().count(), 1);
       
   352     QCOMPARE(watcher.directories().first(), home);
       
   353     watcher.addPath(home);
       
   354     QCOMPARE(watcher.directories().count(), 1);
       
   355 
       
   356     // With empty string
       
   357     QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::addPath: path is empty");
       
   358     watcher.addPath(QString());
       
   359 }
       
   360 
       
   361 void tst_QFileSystemWatcher::removePath()
       
   362 {
       
   363     QFileSystemWatcher watcher;
       
   364     QString home = QDir::homePath();
       
   365     watcher.addPath(home);
       
   366     watcher.removePath(home);
       
   367     QCOMPARE(watcher.directories().count(), 0);
       
   368     watcher.removePath(home);
       
   369     QCOMPARE(watcher.directories().count(), 0);
       
   370 
       
   371     // With empty string
       
   372     QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::removePath: path is empty");
       
   373     watcher.removePath(QString());
       
   374 }
       
   375 
       
   376 void tst_QFileSystemWatcher::addPaths()
       
   377 {
       
   378     QFileSystemWatcher watcher;
       
   379     QStringList paths;
       
   380     paths << QDir::homePath() << QDir::currentPath();
       
   381     watcher.addPaths(paths);
       
   382     QCOMPARE(watcher.directories().count(), 2);
       
   383 
       
   384     // With empty list
       
   385     paths.clear();
       
   386     QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::addPaths: list is empty");
       
   387     watcher.addPaths(paths);
       
   388 }
       
   389 
       
   390 void tst_QFileSystemWatcher::removePaths()
       
   391 {
       
   392     QFileSystemWatcher watcher;
       
   393     QStringList paths;
       
   394     paths << QDir::homePath() << QDir::currentPath();
       
   395     watcher.addPaths(paths);
       
   396     QCOMPARE(watcher.directories().count(), 2);
       
   397     watcher.removePaths(paths);
       
   398     QCOMPARE(watcher.directories().count(), 0);
       
   399 
       
   400     //With empty list
       
   401     paths.clear();
       
   402     QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::removePaths: list is empty");
       
   403     watcher.removePaths(paths);
       
   404 }
       
   405 
       
   406 #if 0
       
   407 class SignalTest : public QObject {
       
   408     Q_OBJECT;
       
   409     public slots:
       
   410         void fileSlot(const QString &file) { qDebug() << "file " << file;}
       
   411         void dirSlot(const QString &dir) { qDebug() << "dir" << dir;}
       
   412 };
       
   413 #endif
       
   414 
       
   415 void tst_QFileSystemWatcher::watchFileAndItsDirectory()
       
   416 {
       
   417     QFETCH(QString, backend);
       
   418     QDir().mkdir("testDir");
       
   419     QDir testDir("testDir");
       
   420 
       
   421     QString testFileName = testDir.filePath("testFile.txt");
       
   422     QString secondFileName = testDir.filePath("testFile2.txt");
       
   423     QFile::remove(secondFileName);
       
   424 
       
   425     QFile testFile(testFileName);
       
   426     testFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
       
   427     testFile.remove();
       
   428 
       
   429     QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate));
       
   430     testFile.write(QByteArray("hello"));
       
   431     testFile.close();
       
   432 
       
   433     QFileSystemWatcher watcher;
       
   434     watcher.setObjectName(QLatin1String("_qt_autotest_force_engine_") + backend);
       
   435 
       
   436     watcher.addPath(testDir.dirName());
       
   437     watcher.addPath(testFileName);
       
   438 
       
   439     /*
       
   440     SignalTest signalTest;
       
   441     QObject::connect(&watcher, SIGNAL(fileChanged(const QString &)), &signalTest, SLOT(fileSlot(const QString &)));
       
   442     QObject::connect(&watcher, SIGNAL(directoryChanged(const QString &)), &signalTest, SLOT(dirSlot(const QString &)));
       
   443     */
       
   444 
       
   445     QSignalSpy fileChangedSpy(&watcher, SIGNAL(fileChanged(const QString &)));
       
   446     QSignalSpy dirChangedSpy(&watcher, SIGNAL(directoryChanged(const QString &)));
       
   447     QEventLoop eventLoop;
       
   448     QTimer timer;
       
   449     connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
       
   450 
       
   451     // resolution of the modification time is system dependent, but it's at most 1 second when using
       
   452     // the polling engine. From what I know, FAT32 has a 2 second resolution. So we have to
       
   453     // wait before modifying the directory...
       
   454     QTest::qWait(2000);
       
   455 
       
   456     QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate));
       
   457     testFile.write(QByteArray("hello again"));
       
   458     testFile.close();
       
   459 
       
   460     timer.start(3000);
       
   461     eventLoop.exec();
       
   462     QVERIFY(fileChangedSpy.count() > 0);
       
   463     //according to Qt 4 documentation:
       
   464     //void QFileSystemWatcher::directoryChanged ( const QString & path )   [signal]
       
   465     //This signal is emitted when the directory at a specified path, is modified
       
   466     //(e.g., when a file is added, -->modified<-- or deleted) or removed from disk.
       
   467     //Note that if there are several changes during a short period of time, some
       
   468     //of the changes might not emit this signal. However, the last change in the
       
   469     //sequence of changes will always generate this signal.
       
   470     //Symbian behaves as documented (and can't be filtered), but the other platforms don't
       
   471     //so test should not assert this
       
   472     QVERIFY(dirChangedSpy.count() < 2);
       
   473 
       
   474     if (backend == "dnotify")
       
   475         QSKIP("dnotify is broken, skipping the rest of the test.", SkipSingle);
       
   476 
       
   477     fileChangedSpy.clear();
       
   478     dirChangedSpy.clear();
       
   479     QFile secondFile(secondFileName);
       
   480     secondFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
       
   481     secondFile.write("Foo");
       
   482     secondFile.close();
       
   483 
       
   484     timer.start(3000);
       
   485     eventLoop.exec();
       
   486     QCOMPARE(fileChangedSpy.count(), 0);
       
   487 #ifdef Q_OS_WINCE
       
   488     QEXPECT_FAIL("poller", "Directory does not get updated on file removal(See #137910)", Abort);
       
   489 #endif
       
   490     QCOMPARE(dirChangedSpy.count(), 1);
       
   491 
       
   492     dirChangedSpy.clear();
       
   493 
       
   494     QFile::remove(testFileName);
       
   495 
       
   496     timer.start(3000);
       
   497     eventLoop.exec();
       
   498     QVERIFY(fileChangedSpy.count() > 0);
       
   499     QCOMPARE(dirChangedSpy.count(), 1);
       
   500 
       
   501     fileChangedSpy.clear();
       
   502     dirChangedSpy.clear();
       
   503 
       
   504     watcher.removePath(testFileName);
       
   505     QFile::remove(secondFileName);
       
   506 
       
   507     timer.start(3000);
       
   508     eventLoop.exec();
       
   509     QCOMPARE(fileChangedSpy.count(), 0);
       
   510     // polling watcher has generated separate events for content and time change
       
   511     // on Symbian emulator, so allow possibility of 2 events
       
   512     QVERIFY(dirChangedSpy.count() == 1 || dirChangedSpy.count() == 2);
       
   513 
       
   514     QVERIFY(QDir().rmdir("testDir"));
       
   515 }
       
   516 
       
   517 void tst_QFileSystemWatcher::cleanup()
       
   518 {
       
   519     QDir testDir("testDir");
       
   520     QString testFileName = testDir.filePath("testFile.txt");
       
   521     QString secondFileName = testDir.filePath("testFile2.txt");
       
   522     QFile::remove(testFileName);
       
   523     QFile::remove(secondFileName);
       
   524     QDir().rmdir("testDir");
       
   525 }
       
   526 
       
   527 void tst_QFileSystemWatcher::nonExistingFile()
       
   528 {
       
   529     // Don't crash...
       
   530     QFileSystemWatcher watcher;
       
   531     watcher.addPath("file_that_does_not_exist.txt");
       
   532     QVERIFY(true);
       
   533 }
       
   534 
       
   535 QTEST_MAIN(tst_QFileSystemWatcher)
       
   536 #include "tst_qfilesystemwatcher.moc"