mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-04 16:25:27 +08:00
qt 6.5.1 original
This commit is contained in:
15
tests/auto/corelib/io/qfilesystemwatcher/BLACKLIST
Normal file
15
tests/auto/corelib/io/qfilesystemwatcher/BLACKLIST
Normal file
@ -0,0 +1,15 @@
|
||||
# QTBUG-33574 QTBUG-30943
|
||||
[signalsEmittedAfterFileMoved]
|
||||
windows
|
||||
[watchFileAndItsDirectory:native backend-testfile]
|
||||
osx
|
||||
windows
|
||||
[watchFileAndItsDirectory:native backend-specialchars]
|
||||
osx
|
||||
windows
|
||||
# QTBUG-102095
|
||||
[watchDirectory]
|
||||
macos arm ci
|
||||
# QTBUG-102096
|
||||
[signalsEmittedAfterFileMoved]
|
||||
macos arm ci
|
11
tests/auto/corelib/io/qfilesystemwatcher/CMakeLists.txt
Normal file
11
tests/auto/corelib/io/qfilesystemwatcher/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#####################################################################
|
||||
## tst_qfilesystemwatcher Test:
|
||||
#####################################################################
|
||||
|
||||
qt_internal_add_test(tst_qfilesystemwatcher
|
||||
SOURCES
|
||||
tst_qfilesystemwatcher.cpp
|
||||
)
|
@ -0,0 +1,843 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#include <QTest>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <QTemporaryDir>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QElapsedTimer>
|
||||
#include <QTextStream>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QSignalSpy>
|
||||
#include <QTimer>
|
||||
#include <QTemporaryFile>
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <qt_windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <QStandardPaths>
|
||||
#endif
|
||||
|
||||
/* All tests need to run in temporary directories not used
|
||||
* by the application to avoid non-deterministic failures on Windows
|
||||
* due to locked directories and left-overs from previous tests. */
|
||||
|
||||
class tst_QFileSystemWatcher : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
tst_QFileSystemWatcher();
|
||||
|
||||
private slots:
|
||||
#ifdef QT_BUILD_INTERNAL
|
||||
void basicTest_data();
|
||||
void basicTest();
|
||||
|
||||
void watchDirectory_data();
|
||||
void watchDirectory();
|
||||
#endif
|
||||
|
||||
void addPath();
|
||||
void removePath();
|
||||
void addPaths();
|
||||
void removePaths();
|
||||
void removePathsFilesInSameDirectory();
|
||||
|
||||
#ifdef QT_BUILD_INTERNAL
|
||||
void watchFileAndItsDirectory_data() { basicTest_data(); }
|
||||
void watchFileAndItsDirectory();
|
||||
#endif
|
||||
|
||||
void nonExistingFile();
|
||||
|
||||
void removeFileAndUnWatch();
|
||||
|
||||
void destroyAfterQCoreApplication();
|
||||
|
||||
#ifdef QT_BUILD_INTERNAL
|
||||
void QTBUG2331();
|
||||
void QTBUG2331_data() { basicTest_data(); }
|
||||
#endif
|
||||
|
||||
void signalsEmittedAfterFileMoved();
|
||||
|
||||
void watchUnicodeCharacters();
|
||||
#if defined(Q_OS_WIN)
|
||||
void watchDirectoryAttributeChanges();
|
||||
#endif
|
||||
|
||||
private:
|
||||
QString m_tempDirPattern;
|
||||
};
|
||||
|
||||
tst_QFileSystemWatcher::tst_QFileSystemWatcher()
|
||||
{
|
||||
m_tempDirPattern = QDir::tempPath();
|
||||
if (!m_tempDirPattern.endsWith(QLatin1Char('/')))
|
||||
m_tempDirPattern += QLatin1Char('/');
|
||||
m_tempDirPattern += QStringLiteral("tst_qfilesystemwatcherXXXXXX");
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
QDir::setCurrent(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef QT_BUILD_INTERNAL
|
||||
void tst_QFileSystemWatcher::basicTest_data()
|
||||
{
|
||||
QTest::addColumn<QString>("backend");
|
||||
QTest::addColumn<QString>("testFileName");
|
||||
const QString testFile = QStringLiteral("testfile.txt");
|
||||
// QTBUG-31341: Test the UNICODE capabilities; ensure no QString::toLower()
|
||||
// is in the code path since that will lower case for example
|
||||
// LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE with context, whereas the Windows file
|
||||
// system will not.
|
||||
const QString specialCharacterFile =
|
||||
QString(QChar(ushort(0x130))) // LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE
|
||||
+ QChar(ushort(0x00DC)) // LATIN_CAPITAL_LETTER_U_WITH_DIAERESIS
|
||||
+ QStringLiteral(".txt");
|
||||
|
||||
#if !defined(QT_NO_INOTIFY)
|
||||
QTest::newRow("native backend-testfile") << "native" << testFile;
|
||||
QTest::newRow("native backend-specialchars") << "native" << specialCharacterFile;
|
||||
#endif
|
||||
QTest::newRow("poller backend-testfile") << "poller" << testFile;
|
||||
}
|
||||
|
||||
void tst_QFileSystemWatcher::basicTest()
|
||||
{
|
||||
QFETCH(QString, backend);
|
||||
QFETCH(QString, testFileName);
|
||||
|
||||
// create test file
|
||||
QTemporaryDir temporaryDirectory(m_tempDirPattern);
|
||||
QVERIFY2(temporaryDirectory.isValid(), qPrintable(temporaryDirectory.errorString()));
|
||||
QFile testFile(temporaryDirectory.path() + QLatin1Char('/') + testFileName);
|
||||
QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate));
|
||||
testFile.write(QByteArray("hello"));
|
||||
testFile.close();
|
||||
|
||||
// set some file permissions
|
||||
testFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner);
|
||||
|
||||
// create watcher, forcing it to use a specific backend
|
||||
QFileSystemWatcher watcher;
|
||||
watcher.setObjectName(QLatin1String("_qt_autotest_force_engine_") + backend);
|
||||
QVERIFY(watcher.addPath(testFile.fileName()));
|
||||
|
||||
QSignalSpy changedSpy(&watcher, &QFileSystemWatcher::fileChanged);
|
||||
QVERIFY(changedSpy.isValid());
|
||||
QEventLoop eventLoop;
|
||||
QTimer timer;
|
||||
connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
|
||||
|
||||
// modify the file, should get a signal from the watcher
|
||||
|
||||
// resolution of the modification time is system dependent, but it's at most 1 second when using
|
||||
// the polling engine. I've heard rumors that FAT32 has a 2 second resolution. So, we have to
|
||||
// wait a bit before we can modify the file (hrmph)...
|
||||
QTest::qWait(2000);
|
||||
|
||||
testFile.open(QIODevice::WriteOnly | QIODevice::Append);
|
||||
testFile.write(QByteArray("world"));
|
||||
testFile.close();
|
||||
|
||||
// waiting max 5 seconds for notification for file modification to trigger
|
||||
QTRY_COMPARE(changedSpy.size(), 1);
|
||||
QCOMPARE(changedSpy.at(0).size(), 1);
|
||||
|
||||
QString fileName = changedSpy.at(0).at(0).toString();
|
||||
QCOMPARE(fileName, testFile.fileName());
|
||||
|
||||
changedSpy.clear();
|
||||
|
||||
// remove the watch and modify the file, should not get a signal from the watcher
|
||||
QVERIFY(watcher.removePath(testFile.fileName()));
|
||||
testFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
testFile.write(QByteArray("hello universe!"));
|
||||
testFile.close();
|
||||
|
||||
// waiting max 5 seconds for notification for file modification to trigger
|
||||
timer.start(5000);
|
||||
eventLoop.exec();
|
||||
|
||||
QCOMPARE(changedSpy.size(), 0);
|
||||
|
||||
// readd the file watch with a relative path
|
||||
const QString relativeTestFileName = QDir::current().relativeFilePath(testFile.fileName());
|
||||
QVERIFY(!relativeTestFileName.isEmpty());
|
||||
QVERIFY(watcher.addPath(relativeTestFileName));
|
||||
testFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
testFile.write(QByteArray("hello multiverse!"));
|
||||
testFile.close();
|
||||
|
||||
QTRY_VERIFY(changedSpy.size() > 0);
|
||||
|
||||
QVERIFY(watcher.removePath(relativeTestFileName));
|
||||
|
||||
changedSpy.clear();
|
||||
|
||||
// readd the file watch
|
||||
QVERIFY(watcher.addPath(testFile.fileName()));
|
||||
|
||||
// change the permissions, should get a signal from the watcher
|
||||
testFile.setPermissions(QFile::ReadOwner);
|
||||
|
||||
// IN_ATTRIB doesn't work on QNX, so skip this test
|
||||
#if !defined(Q_OS_QNX)
|
||||
|
||||
// waiting max 5 seconds for notification for file permission modification to trigger
|
||||
QTRY_COMPARE(changedSpy.size(), 1);
|
||||
QCOMPARE(changedSpy.at(0).size(), 1);
|
||||
|
||||
fileName = changedSpy.at(0).at(0).toString();
|
||||
QCOMPARE(fileName, testFile.fileName());
|
||||
|
||||
#endif
|
||||
|
||||
changedSpy.clear();
|
||||
|
||||
// remove the watch and modify file permissions, should not get a signal from the watcher
|
||||
QVERIFY(watcher.removePath(testFile.fileName()));
|
||||
testFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOther);
|
||||
|
||||
// waiting max 5 seconds for notification for file modification to trigger
|
||||
timer.start(5000);
|
||||
eventLoop.exec();
|
||||
|
||||
QCOMPARE(changedSpy.size(), 0);
|
||||
|
||||
// readd the file watch
|
||||
QVERIFY(watcher.addPath(testFile.fileName()));
|
||||
|
||||
// remove the file, should get a signal from the watcher
|
||||
QVERIFY(testFile.remove());
|
||||
|
||||
// waiting max 5 seconds for notification for file removal to trigger
|
||||
// > 0 && < 3 because some platforms may emit two changes
|
||||
// XXX: which platforms? (QTBUG-23370)
|
||||
QTRY_VERIFY(changedSpy.size() > 0 && changedSpy.size() < 3);
|
||||
QCOMPARE(changedSpy.at(0).size(), 1);
|
||||
|
||||
fileName = changedSpy.at(0).at(0).toString();
|
||||
QCOMPARE(fileName, testFile.fileName());
|
||||
|
||||
changedSpy.clear();
|
||||
|
||||
// recreate the file, we should not get any notification
|
||||
QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate));
|
||||
testFile.write(QByteArray("hello"));
|
||||
testFile.close();
|
||||
|
||||
// waiting max 5 seconds for notification for file recreation to trigger
|
||||
timer.start(5000);
|
||||
eventLoop.exec();
|
||||
|
||||
QCOMPARE(changedSpy.size(), 0);
|
||||
|
||||
QVERIFY(testFile.remove());
|
||||
}
|
||||
|
||||
void tst_QFileSystemWatcher::watchDirectory_data()
|
||||
{
|
||||
QTest::addColumn<QString>("backend");
|
||||
QTest::addColumn<QStringList>("testDirNames");
|
||||
const QStringList testDirNames = {QStringLiteral("testdir"), QStringLiteral("testdir2")};
|
||||
|
||||
QTest::newRow("native backend") << "native" << testDirNames;
|
||||
QTest::newRow("poller backend") << "poller" << testDirNames;
|
||||
}
|
||||
|
||||
void tst_QFileSystemWatcher::watchDirectory()
|
||||
{
|
||||
QFETCH(QString, backend);
|
||||
|
||||
QTemporaryDir temporaryDirectory(m_tempDirPattern);
|
||||
QVERIFY2(temporaryDirectory.isValid(), qPrintable(temporaryDirectory.errorString()));
|
||||
|
||||
QFETCH(QStringList, testDirNames);
|
||||
|
||||
QDir temporaryDir(temporaryDirectory.path());
|
||||
QStringList testDirs;
|
||||
QStringList testFiles;
|
||||
|
||||
for (const auto &testDirName : testDirNames) {
|
||||
QVERIFY(temporaryDir.mkdir(testDirName));
|
||||
QDir testDir = temporaryDir;
|
||||
QVERIFY(testDir.cd(testDirName));
|
||||
|
||||
testFiles.append(testDir.filePath("testFile.txt"));
|
||||
QFile::remove(testFiles.last());
|
||||
testDirs.append(testDir.absolutePath());
|
||||
}
|
||||
|
||||
QFileSystemWatcher watcher;
|
||||
watcher.setObjectName(QLatin1String("_qt_autotest_force_engine_") + backend);
|
||||
QVERIFY(watcher.addPaths(testDirs).isEmpty());
|
||||
|
||||
QSignalSpy changedSpy(&watcher, &QFileSystemWatcher::directoryChanged);
|
||||
QVERIFY(changedSpy.isValid());
|
||||
QEventLoop eventLoop;
|
||||
QTimer timer;
|
||||
connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
|
||||
|
||||
// resolution of the modification time is system dependent, but it's at most 1 second when using
|
||||
// the polling engine. From what I know, FAT32 has a 2 second resolution. So we have to
|
||||
// wait before modifying the directory...
|
||||
QTest::qWait(2000);
|
||||
// remove the watch, should not get notification of a new file
|
||||
QVERIFY(watcher.removePaths(testDirs).isEmpty());
|
||||
for (const auto &testFileName : testFiles) {
|
||||
QFile testFile(testFileName);
|
||||
QVERIFY(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate));
|
||||
testFile.close();
|
||||
}
|
||||
|
||||
// waiting max 5 seconds for notification for file recreationg to trigger
|
||||
timer.start(5000);
|
||||
eventLoop.exec();
|
||||
|
||||
QCOMPARE(changedSpy.size(), 0);
|
||||
|
||||
QVERIFY(watcher.addPaths(testDirs).isEmpty());
|
||||
|
||||
// remove the file again, should get a signal from the watcher
|
||||
for (const auto &testFileName : testFiles)
|
||||
QVERIFY(QFile::remove(testFileName));
|
||||
|
||||
timer.start(5000);
|
||||
eventLoop.exec();
|
||||
|
||||
// remove the directory, should get a signal from the watcher
|
||||
for (const auto &testDirName : testDirs)
|
||||
QVERIFY(temporaryDir.rmdir(testDirName));
|
||||
|
||||
QMap<QString, int> signalCounter;
|
||||
for (const auto &testDirName : testDirs)
|
||||
signalCounter[testDirName] = 0;
|
||||
|
||||
// waiting max 5 seconds for notification for directory removal to trigger
|
||||
QTRY_COMPARE(changedSpy.size(), testDirs.size() * 2);
|
||||
for (int i = 0; i < changedSpy.size(); i++) {
|
||||
const auto &signal = changedSpy.at(i);
|
||||
QCOMPARE(signal.size(), 1);
|
||||
|
||||
auto it = signalCounter.find(signal.at(0).toString());
|
||||
QVERIFY(it != signalCounter.end());
|
||||
QVERIFY(it.value() < 2);
|
||||
it.value()++;
|
||||
}
|
||||
|
||||
for (const auto &count : signalCounter)
|
||||
QCOMPARE(count, 2);
|
||||
|
||||
// flush pending signals (like the one from the rmdir above)
|
||||
timer.start(5000);
|
||||
eventLoop.exec();
|
||||
changedSpy.clear();
|
||||
|
||||
// recreate the file, we should not get any notification
|
||||
for (const auto &testDirName : testDirNames) {
|
||||
if (!temporaryDir.mkdir(testDirName)) {
|
||||
QSKIP(qPrintable(QString::fromLatin1("Failed to recreate directory '%1' under '%2', skipping final test.").
|
||||
arg(testDirName, temporaryDir.absolutePath())));
|
||||
}
|
||||
}
|
||||
|
||||
// waiting max 5 seconds for notification for dir recreation to trigger
|
||||
timer.start(5000);
|
||||
eventLoop.exec();
|
||||
|
||||
QCOMPARE(changedSpy.size(), 0);
|
||||
|
||||
for (const auto &testDirName : testDirs)
|
||||
QVERIFY(temporaryDir.rmdir(testDirName));
|
||||
}
|
||||
#endif // QT_BUILD_INTERNAL
|
||||
|
||||
void tst_QFileSystemWatcher::addPath()
|
||||
{
|
||||
QFileSystemWatcher watcher;
|
||||
QString home = QDir::homePath();
|
||||
QVERIFY(watcher.addPath(home));
|
||||
QCOMPARE(watcher.directories().size(), 1);
|
||||
QCOMPARE(watcher.directories().first(), home);
|
||||
|
||||
// second watch on an already-watched path should fail
|
||||
QVERIFY(!watcher.addPath(home));
|
||||
QCOMPARE(watcher.directories().size(), 1);
|
||||
|
||||
// With empty string
|
||||
QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::addPath: path is empty");
|
||||
QVERIFY(watcher.addPath(QString()));
|
||||
}
|
||||
|
||||
void tst_QFileSystemWatcher::removePath()
|
||||
{
|
||||
QFileSystemWatcher watcher;
|
||||
QString home = QDir::homePath();
|
||||
QVERIFY(watcher.addPath(home));
|
||||
QVERIFY(watcher.removePath(home));
|
||||
QCOMPARE(watcher.directories().size(), 0);
|
||||
QVERIFY(!watcher.removePath(home));
|
||||
QCOMPARE(watcher.directories().size(), 0);
|
||||
|
||||
// With empty string
|
||||
QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::removePath: path is empty");
|
||||
QVERIFY(watcher.removePath(QString()));
|
||||
}
|
||||
|
||||
void tst_QFileSystemWatcher::addPaths()
|
||||
{
|
||||
QFileSystemWatcher watcher;
|
||||
QStringList paths;
|
||||
paths << QDir::homePath() << QDir::tempPath();
|
||||
#ifndef Q_OS_QNX
|
||||
// Adding this makes QNX fail and we haven't investigated why
|
||||
for (const QFileInfo &fi : QDir::drives())
|
||||
paths << fi.absoluteFilePath(); // on Unix, this will be just "/"
|
||||
#endif
|
||||
|
||||
QCOMPARE(watcher.addPaths(paths), QStringList());
|
||||
QCOMPARE(watcher.directories().size(), paths.size());
|
||||
|
||||
// With empty list
|
||||
paths.clear();
|
||||
QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::addPaths: list is empty");
|
||||
QCOMPARE(watcher.addPaths(paths), QStringList());
|
||||
}
|
||||
|
||||
// A signal spy that records the paths and times received for better diagnostics.
|
||||
class FileSystemWatcherSpy : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Mode {
|
||||
SpyOnDirectoryChanged,
|
||||
SpyOnFileChanged
|
||||
};
|
||||
|
||||
explicit FileSystemWatcherSpy(QFileSystemWatcher *watcher, Mode mode)
|
||||
{
|
||||
connect(watcher, mode == SpyOnDirectoryChanged ?
|
||||
&QFileSystemWatcher::directoryChanged : &QFileSystemWatcher::fileChanged,
|
||||
this, &FileSystemWatcherSpy::spySlot);
|
||||
m_elapsedTimer.start();
|
||||
}
|
||||
|
||||
int count() const { return m_entries.size(); }
|
||||
void clear()
|
||||
{
|
||||
m_entries.clear();
|
||||
m_elapsedTimer.restart();
|
||||
}
|
||||
|
||||
QByteArray receivedFilesMessage() const
|
||||
{
|
||||
QString result;
|
||||
QTextStream str(&result);
|
||||
str << "At " << m_elapsedTimer.elapsed() << "ms, received "
|
||||
<< count() << " changes: ";
|
||||
for (int i =0, e = m_entries.size(); i < e; ++i) {
|
||||
if (i)
|
||||
str << ", ";
|
||||
str << m_entries.at(i).timeStamp << "ms: " << QDir::toNativeSeparators(m_entries.at(i).path);
|
||||
}
|
||||
return result.toLocal8Bit();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void spySlot(const QString &p) { m_entries.append(Entry(m_elapsedTimer.elapsed(), p)); }
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
Entry() : timeStamp(0) {}
|
||||
Entry(qint64 t, const QString &p) : timeStamp(t), path(p) {}
|
||||
|
||||
qint64 timeStamp;
|
||||
QString path;
|
||||
};
|
||||
|
||||
QElapsedTimer m_elapsedTimer;
|
||||
QList<Entry> m_entries;
|
||||
};
|
||||
|
||||
void tst_QFileSystemWatcher::removePaths()
|
||||
{
|
||||
QFileSystemWatcher watcher;
|
||||
QStringList paths;
|
||||
paths << QDir::homePath() << QDir::tempPath();
|
||||
#ifndef Q_OS_QNX
|
||||
// Adding this makes QNX fail and we haven't investigated why
|
||||
for (const QFileInfo &fi : QDir::drives())
|
||||
paths << fi.absoluteFilePath(); // on Unix, this will be just "/"
|
||||
#endif
|
||||
|
||||
QCOMPARE(watcher.addPaths(paths), QStringList());
|
||||
QCOMPARE(watcher.directories().size(), paths.size());
|
||||
QCOMPARE(watcher.removePaths(paths), QStringList());
|
||||
QCOMPARE(watcher.directories().size(), 0);
|
||||
|
||||
//With empty list
|
||||
paths.clear();
|
||||
QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::removePaths: list is empty");
|
||||
watcher.removePaths(paths);
|
||||
}
|
||||
|
||||
void tst_QFileSystemWatcher::removePathsFilesInSameDirectory()
|
||||
{
|
||||
// QTBUG-46449/Windows: Check the return values of removePaths().
|
||||
// When adding the 1st file, a thread is started to watch the temp path.
|
||||
// After adding and removing the 2nd file, the thread is still running and
|
||||
// success should be reported.
|
||||
QTemporaryFile file1(m_tempDirPattern);
|
||||
QTemporaryFile file2(m_tempDirPattern);
|
||||
QVERIFY2(file1.open(), qPrintable(file1.errorString()));
|
||||
QVERIFY2(file2.open(), qPrintable(file1.errorString()));
|
||||
const QString path1 = file1.fileName();
|
||||
const QString path2 = file2.fileName();
|
||||
file1.close();
|
||||
file2.close();
|
||||
QFileSystemWatcher watcher;
|
||||
QVERIFY(watcher.addPath(path1));
|
||||
QCOMPARE(watcher.files().size(), 1);
|
||||
QVERIFY(watcher.addPath(path2));
|
||||
QCOMPARE(watcher.files().size(), 2);
|
||||
QVERIFY(watcher.removePath(path1));
|
||||
QCOMPARE(watcher.files().size(), 1);
|
||||
QVERIFY(watcher.removePath(path2));
|
||||
QCOMPARE(watcher.files().size(), 0);
|
||||
}
|
||||
|
||||
#ifdef QT_BUILD_INTERNAL
|
||||
static QByteArray msgFileOperationFailed(const char *what, const QFile &f)
|
||||
{
|
||||
return what + QByteArrayLiteral(" failed on \"")
|
||||
+ QDir::toNativeSeparators(f.fileName()).toLocal8Bit()
|
||||
+ QByteArrayLiteral("\": ") + f.errorString().toLocal8Bit();
|
||||
}
|
||||
|
||||
void tst_QFileSystemWatcher::watchFileAndItsDirectory()
|
||||
{
|
||||
QFETCH(QString, backend);
|
||||
|
||||
QTemporaryDir temporaryDirectory(m_tempDirPattern);
|
||||
QVERIFY2(temporaryDirectory.isValid(), qPrintable(temporaryDirectory.errorString()));
|
||||
|
||||
QDir temporaryDir(temporaryDirectory.path());
|
||||
const QString testDirName = QStringLiteral("testDir");
|
||||
QVERIFY(temporaryDir.mkdir(testDirName));
|
||||
QDir testDir = temporaryDir;
|
||||
QVERIFY(testDir.cd(testDirName));
|
||||
|
||||
QString testFileName = testDir.filePath("testFile.txt");
|
||||
QString secondFileName = testDir.filePath("testFile2.txt");
|
||||
|
||||
QFile testFile(testFileName);
|
||||
QVERIFY2(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate), msgFileOperationFailed("open", testFile));
|
||||
QVERIFY2(testFile.write(QByteArrayLiteral("hello")) > 0, msgFileOperationFailed("write", testFile));
|
||||
testFile.close();
|
||||
|
||||
QFileSystemWatcher watcher;
|
||||
watcher.setObjectName(QLatin1String("_qt_autotest_force_engine_") + backend);
|
||||
|
||||
QVERIFY(watcher.addPath(testDir.absolutePath()));
|
||||
QVERIFY(watcher.addPath(testFileName));
|
||||
|
||||
QSignalSpy fileChangedSpy(&watcher, &QFileSystemWatcher::fileChanged);
|
||||
FileSystemWatcherSpy dirChangedSpy(&watcher, FileSystemWatcherSpy::SpyOnDirectoryChanged);
|
||||
QVERIFY(fileChangedSpy.isValid());
|
||||
QEventLoop eventLoop;
|
||||
QTimer timer;
|
||||
connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
|
||||
|
||||
// resolution of the modification time is system dependent, but it's at most 1 second when using
|
||||
// the polling engine. From what I know, FAT32 has a 2 second resolution. So we have to
|
||||
// wait before modifying the directory...
|
||||
QTest::qWait(2000);
|
||||
|
||||
QVERIFY2(testFile.open(QIODevice::WriteOnly | QIODevice::Truncate), msgFileOperationFailed("open", testFile));
|
||||
QVERIFY2(testFile.write(QByteArrayLiteral("hello again")), msgFileOperationFailed("write", testFile));
|
||||
testFile.close();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// wait again for the file's atime to be updated
|
||||
QTest::qWait(2000);
|
||||
#endif
|
||||
|
||||
QTRY_VERIFY(fileChangedSpy.size() > 0);
|
||||
QVERIFY2(dirChangedSpy.count() == 0, dirChangedSpy.receivedFilesMessage());
|
||||
|
||||
fileChangedSpy.clear();
|
||||
QFile secondFile(secondFileName);
|
||||
QVERIFY2(secondFile.open(QIODevice::WriteOnly | QIODevice::Truncate), msgFileOperationFailed("open", secondFile));
|
||||
QVERIFY2(secondFile.write(QByteArrayLiteral("Foo")) > 0, msgFileOperationFailed("write", secondFile));
|
||||
secondFile.close();
|
||||
|
||||
timer.start(3000);
|
||||
eventLoop.exec();
|
||||
int fileChangedSpyCount = fileChangedSpy.size();
|
||||
#ifdef Q_OS_WIN
|
||||
if (fileChangedSpyCount != 0)
|
||||
QEXPECT_FAIL("", "See QTBUG-30943", Continue);
|
||||
#endif
|
||||
QCOMPARE(fileChangedSpyCount, 0);
|
||||
QCOMPARE(dirChangedSpy.count(), 1);
|
||||
|
||||
dirChangedSpy.clear();
|
||||
|
||||
QVERIFY(QFile::remove(testFileName));
|
||||
|
||||
QTRY_VERIFY(fileChangedSpy.size() > 0);
|
||||
QTRY_COMPARE(dirChangedSpy.count(), 1);
|
||||
|
||||
fileChangedSpy.clear();
|
||||
dirChangedSpy.clear();
|
||||
|
||||
// removing a deleted file should fail
|
||||
QVERIFY(!watcher.removePath(testFileName));
|
||||
QVERIFY(QFile::remove(secondFileName));
|
||||
|
||||
timer.start(3000);
|
||||
eventLoop.exec();
|
||||
QCOMPARE(fileChangedSpy.size(), 0);
|
||||
QCOMPARE(dirChangedSpy.count(), 1);
|
||||
|
||||
// QTBUG-61792, removal should succeed (bug on Windows which uses one change
|
||||
// notification per directory).
|
||||
QVERIFY(watcher.removePath(testDir.absolutePath()));
|
||||
|
||||
QVERIFY(temporaryDir.rmdir(testDirName));
|
||||
}
|
||||
#endif // QT_BUILD_INTERNAL
|
||||
|
||||
void tst_QFileSystemWatcher::nonExistingFile()
|
||||
{
|
||||
// Don't crash...
|
||||
QFileSystemWatcher watcher;
|
||||
QVERIFY(!watcher.addPath("file_that_does_not_exist.txt"));
|
||||
|
||||
// Test that the paths returned in error aren't messed with
|
||||
QCOMPARE(watcher.addPaths(QStringList() << "../..//./does-not-exist"),
|
||||
QStringList() << "../..//./does-not-exist");
|
||||
|
||||
// empty path is not actually a failure
|
||||
QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::addPaths: list is empty");
|
||||
QCOMPARE(watcher.addPaths(QStringList() << QString()), QStringList());
|
||||
|
||||
// empty path is not actually a failure
|
||||
QTest::ignoreMessage(QtWarningMsg, "QFileSystemWatcher::removePaths: list is empty");
|
||||
QCOMPARE(watcher.removePaths(QStringList() << QString()), QStringList());
|
||||
}
|
||||
|
||||
void tst_QFileSystemWatcher::removeFileAndUnWatch()
|
||||
{
|
||||
QTemporaryDir temporaryDirectory(m_tempDirPattern);
|
||||
QVERIFY2(temporaryDirectory.isValid(), qPrintable(temporaryDirectory.errorString()));
|
||||
|
||||
const QString filename = temporaryDirectory.path() + QStringLiteral("/foo.txt");
|
||||
|
||||
QFileSystemWatcher watcher;
|
||||
|
||||
{
|
||||
QFile testFile(filename);
|
||||
QVERIFY2(testFile.open(QIODevice::WriteOnly),
|
||||
qPrintable(QString::fromLatin1("Cannot open %1 for writing: %2").arg(filename, testFile.errorString())));
|
||||
testFile.close();
|
||||
}
|
||||
QVERIFY(watcher.addPath(filename));
|
||||
|
||||
QFile::remove(filename);
|
||||
/* There are potential race conditions here; the watcher thread might remove the file from its list
|
||||
* before the call to watcher.removePath(), which then fails. When that happens, the auto-signal
|
||||
* notification to remove the file from the watcher's main list will not be delivered before the next
|
||||
* event loop such that the call to watcher.addPath() fails since the file is still in the main list. */
|
||||
if (!watcher.removePath(filename))
|
||||
QSKIP("Skipping remaining test due to race condition.");
|
||||
|
||||
{
|
||||
QFile testFile(filename);
|
||||
QVERIFY2(testFile.open(QIODevice::WriteOnly),
|
||||
qPrintable(QString::fromLatin1("Cannot open %1 for writing: %2").arg(filename, testFile.errorString())));
|
||||
testFile.close();
|
||||
}
|
||||
QVERIFY(watcher.addPath(filename));
|
||||
}
|
||||
|
||||
class SomeSingleton : public QObject
|
||||
{
|
||||
public:
|
||||
SomeSingleton() : mFsWatcher(new QFileSystemWatcher(this)) { mFsWatcher->addPath(QLatin1String("/usr/lib"));}
|
||||
void bla() const {}
|
||||
QFileSystemWatcher* mFsWatcher;
|
||||
};
|
||||
|
||||
Q_GLOBAL_STATIC(SomeSingleton, someSingleton)
|
||||
|
||||
// This is a regression test for QTBUG-15255, where a deadlock occurred if a
|
||||
// QFileSystemWatcher was destroyed after the QCoreApplication instance had
|
||||
// been destroyed. There are no explicit verification steps in this test --
|
||||
// it is sufficient that the test terminates.
|
||||
void tst_QFileSystemWatcher::destroyAfterQCoreApplication()
|
||||
{
|
||||
someSingleton()->bla();
|
||||
QTest::qWait(30);
|
||||
}
|
||||
|
||||
#ifdef QT_BUILD_INTERNAL
|
||||
// regression test for QTBUG2331.
|
||||
// essentially, on windows, directories were not unwatched after being deleted
|
||||
// from the disk, causing all sorts of interesting problems.
|
||||
void tst_QFileSystemWatcher::QTBUG2331()
|
||||
{
|
||||
QFETCH(QString, backend);
|
||||
|
||||
QTemporaryDir temporaryDirectory(m_tempDirPattern);
|
||||
QVERIFY2(temporaryDirectory.isValid(), qPrintable(temporaryDirectory.errorString()));
|
||||
QFileSystemWatcher watcher;
|
||||
watcher.setObjectName(QLatin1String("_qt_autotest_force_engine_") + backend);
|
||||
QVERIFY(watcher.addPath(temporaryDirectory.path()));
|
||||
|
||||
// watch signal
|
||||
QSignalSpy changedSpy(&watcher, &QFileSystemWatcher::directoryChanged);
|
||||
QVERIFY(changedSpy.isValid());
|
||||
|
||||
// remove directory, we should get one change signal, and we should no longer
|
||||
// be watching the directory.
|
||||
QVERIFY(temporaryDirectory.remove());
|
||||
QTRY_COMPARE(changedSpy.size(), 1);
|
||||
QCOMPARE(watcher.directories(), QStringList());
|
||||
}
|
||||
#endif // QT_BUILD_INTERNAL
|
||||
|
||||
class SignalReceiver : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SignalReceiver(const QDir &moveSrcDir,
|
||||
const QString &moveDestination,
|
||||
QFileSystemWatcher *watcher,
|
||||
QObject *parent = nullptr)
|
||||
: QObject(parent),
|
||||
added(false),
|
||||
moveSrcDir(moveSrcDir),
|
||||
moveDestination(QDir(moveDestination)),
|
||||
watcher(watcher)
|
||||
{}
|
||||
|
||||
public slots:
|
||||
void fileChanged(const QString &path)
|
||||
{
|
||||
QFileInfo finfo(path);
|
||||
|
||||
QCOMPARE(finfo.absolutePath(), moveSrcDir.absolutePath());
|
||||
|
||||
if (!added) {
|
||||
foreach (const QFileInfo &fi, moveDestination.entryInfoList(QDir::Files | QDir::NoSymLinks))
|
||||
watcher->addPath(fi.absoluteFilePath());
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool added;
|
||||
QDir moveSrcDir;
|
||||
QDir moveDestination;
|
||||
QFileSystemWatcher *watcher;
|
||||
};
|
||||
|
||||
// regression test for QTBUG-33211.
|
||||
// using inotify backend if a file is moved and then added to the watcher
|
||||
// before all the fileChanged signals are emitted the remaining signals are
|
||||
// emitted with the destination path instead of the starting path
|
||||
void tst_QFileSystemWatcher::signalsEmittedAfterFileMoved()
|
||||
{
|
||||
const int fileCount = 10;
|
||||
QTemporaryDir temporaryDirectory(m_tempDirPattern);
|
||||
QVERIFY2(temporaryDirectory.isValid(), qPrintable(temporaryDirectory.errorString()));
|
||||
|
||||
QDir testDir(temporaryDirectory.path());
|
||||
QVERIFY(testDir.mkdir("movehere"));
|
||||
QString movePath = testDir.filePath("movehere");
|
||||
|
||||
for (int i = 0; i < fileCount; ++i) {
|
||||
const QByteArray iB = QByteArray::number(i);
|
||||
QFile f(testDir.filePath(QLatin1String("test") + QString::fromLatin1(iB) + QLatin1String(".txt")));
|
||||
QVERIFY(f.open(QIODevice::WriteOnly));
|
||||
f.write(QByteArray("i am ") + iB);
|
||||
f.close();
|
||||
}
|
||||
|
||||
QFileSystemWatcher watcher;
|
||||
QVERIFY(watcher.addPath(testDir.path()));
|
||||
QVERIFY(watcher.addPath(movePath));
|
||||
|
||||
// add files to watcher
|
||||
QFileInfoList files = testDir.entryInfoList(QDir::Files | QDir::NoSymLinks);
|
||||
QCOMPARE(files.size(), fileCount);
|
||||
foreach (const QFileInfo &finfo, files)
|
||||
QVERIFY(watcher.addPath(finfo.absoluteFilePath()));
|
||||
|
||||
// create the signal receiver
|
||||
SignalReceiver signalReceiver(testDir, movePath, &watcher);
|
||||
connect(&watcher, SIGNAL(fileChanged(QString)), &signalReceiver, SLOT(fileChanged(QString)));
|
||||
|
||||
// watch signals
|
||||
FileSystemWatcherSpy changedSpy(&watcher, FileSystemWatcherSpy::SpyOnFileChanged);
|
||||
QCOMPARE(changedSpy.count(), 0);
|
||||
|
||||
// move files to second directory
|
||||
foreach (const QFileInfo &finfo, files)
|
||||
QVERIFY(testDir.rename(finfo.fileName(), QString("movehere/%2").arg(finfo.fileName())));
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
QVERIFY2(changedSpy.count() <= fileCount, changedSpy.receivedFilesMessage());
|
||||
QTRY_COMPARE(changedSpy.count(), fileCount);
|
||||
}
|
||||
|
||||
void tst_QFileSystemWatcher::watchUnicodeCharacters()
|
||||
{
|
||||
QTemporaryDir temporaryDirectory(m_tempDirPattern);
|
||||
QVERIFY2(temporaryDirectory.isValid(), qPrintable(temporaryDirectory.errorString()));
|
||||
|
||||
QDir testDir(temporaryDirectory.path());
|
||||
const QString subDir(QString::fromLatin1("caf\xe9"));
|
||||
QVERIFY(testDir.mkdir(subDir));
|
||||
testDir = QDir(temporaryDirectory.path() + QDir::separator() + subDir);
|
||||
|
||||
QFileSystemWatcher watcher;
|
||||
QVERIFY(watcher.addPath(testDir.path()));
|
||||
|
||||
FileSystemWatcherSpy changedSpy(&watcher, FileSystemWatcherSpy::SpyOnDirectoryChanged);
|
||||
QCOMPARE(changedSpy.count(), 0);
|
||||
QVERIFY(testDir.mkdir("creme"));
|
||||
QTRY_COMPARE(changedSpy.count(), 1);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
void tst_QFileSystemWatcher::watchDirectoryAttributeChanges()
|
||||
{
|
||||
QTemporaryDir temporaryDirectory(m_tempDirPattern);
|
||||
QVERIFY2(temporaryDirectory.isValid(), qPrintable(temporaryDirectory.errorString()));
|
||||
|
||||
QDir testDir(temporaryDirectory.path());
|
||||
const QString subDir(QString::fromLatin1("attrib_test"));
|
||||
QVERIFY(testDir.mkdir(subDir));
|
||||
testDir = QDir(temporaryDirectory.path() + QDir::separator() + subDir);
|
||||
|
||||
QFileSystemWatcher watcher;
|
||||
QVERIFY(watcher.addPath(temporaryDirectory.path()));
|
||||
FileSystemWatcherSpy changedSpy(&watcher, FileSystemWatcherSpy::SpyOnDirectoryChanged);
|
||||
QCOMPARE(changedSpy.count(), 0);
|
||||
QVERIFY(SetFileAttributes(reinterpret_cast<LPCWSTR>(testDir.absolutePath().utf16()), FILE_ATTRIBUTE_HIDDEN) != 0);
|
||||
QTRY_COMPARE(changedSpy.count(), 1);
|
||||
QVERIFY(SetFileAttributes(reinterpret_cast<LPCWSTR>(testDir.absolutePath().utf16()), FILE_ATTRIBUTE_NORMAL) != 0);
|
||||
QTRY_COMPARE(changedSpy.count(), 2);
|
||||
}
|
||||
#endif
|
||||
|
||||
QTEST_MAIN(tst_QFileSystemWatcher)
|
||||
#include "tst_qfilesystemwatcher.moc"
|
Reference in New Issue
Block a user