From: Christian Kamm Date: Mon, 5 Nov 2018 11:12:49 +0000 (+0100) Subject: Test: Add test for locked file tracking and propagation X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~21^2~468^2~438 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=9d55590d10ab29e81dfab1a3346fb94ca1de7425;p=nextcloud-desktop.git Test: Add test for locked file tracking and propagation --- diff --git a/src/gui/lockwatcher.cpp b/src/gui/lockwatcher.cpp index 3f8030cde..d3edc2352 100644 --- a/src/gui/lockwatcher.cpp +++ b/src/gui/lockwatcher.cpp @@ -38,6 +38,16 @@ void LockWatcher::addFile(const QString &path) _watchedPaths.insert(path); } +void LockWatcher::setCheckInterval(std::chrono::milliseconds interval) +{ + _timer.start(interval.count()); +} + +bool LockWatcher::contains(const QString &path) +{ + return _watchedPaths.contains(path); +} + void LockWatcher::checkFiles() { QSet unlocked; diff --git a/src/gui/lockwatcher.h b/src/gui/lockwatcher.h index 13b845e12..f5e27bac6 100644 --- a/src/gui/lockwatcher.h +++ b/src/gui/lockwatcher.h @@ -51,6 +51,12 @@ public: */ void addFile(const QString &path); + /** Adjusts the default interval for checking whether the lock is still present */ + void setCheckInterval(std::chrono::milliseconds interval); + + /** Whether the path is being watched for lock-changes */ + bool contains(const QString &path); + signals: /** Emitted when one of the watched files is no longer * being locked. */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1568c9354..8c719097e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -58,6 +58,7 @@ nextcloud_add_test(LocalDiscovery "syncenginetestutils.h") nextcloud_add_test(RemoteDiscovery "syncenginetestutils.h") nextcloud_add_test(Permissions "syncenginetestutils.h") nextcloud_add_test(SelectiveSync "syncenginetestutils.h") +nextcloud_add_test(LockedFiles "syncenginetestutils.h;../src/gui/lockwatcher.cpp") nextcloud_add_test(FolderWatcher "${FolderWatcher_SRC}") if( UNIX AND NOT APPLE ) diff --git a/test/testlockedfiles.cpp b/test/testlockedfiles.cpp new file mode 100644 index 000000000..df1e8f8d0 --- /dev/null +++ b/test/testlockedfiles.cpp @@ -0,0 +1,163 @@ +/* + * This software is in the public domain, furnished "as is", without technical + * support, and with no warranty, express or implied, as to its usefulness for + * any purpose. + * + */ + +#include +#include "syncenginetestutils.h" +#include "lockwatcher.h" +#include +#include + +using namespace OCC; + +#ifdef Q_OS_WIN +// pass combination of FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE +HANDLE makeHandle(const QString &file, int shareMode) +{ + const wchar_t *wuri = reinterpret_cast(file.utf16()); + auto handle = CreateFileW( + wuri, + GENERIC_READ | GENERIC_WRITE, + shareMode, + NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (handle == INVALID_HANDLE_VALUE) { + qWarning() << GetLastError(); + } + return handle; +} +#endif + +class TestLockedFiles : public QObject +{ + Q_OBJECT + +private slots: + void testBasicLockFileWatcher() + { + int count = 0; + QString file; + + LockWatcher watcher; + watcher.setCheckInterval(std::chrono::milliseconds(50)); + connect(&watcher, &LockWatcher::fileUnlocked, &watcher, [&](const QString &f) { ++count; file = f; }); + + QString tmpFile; + { + QTemporaryFile tmp; + tmp.setAutoRemove(false); + tmp.open(); + tmpFile = tmp.fileName(); + } + QVERIFY(QFile::exists(tmpFile)); + + QVERIFY(!FileSystem::isFileLocked(tmpFile)); + watcher.addFile(tmpFile); + QVERIFY(watcher.contains(tmpFile)); + + QEventLoop loop; + QTimer::singleShot(120, &loop, [&] { loop.exit(); }); + loop.exec(); + + QCOMPARE(count, 1); + QCOMPARE(file, tmpFile); + QVERIFY(!watcher.contains(tmpFile)); + +#ifdef Q_OS_WIN + auto h = makeHandle(tmpFile, 0); + QVERIFY(FileSystem::isFileLocked(tmpFile)); + watcher.addFile(tmpFile); + + count = 0; + file.clear(); + QThread::msleep(120); + qApp->processEvents(); + + QCOMPARE(count, 0); + QVERIFY(file.isEmpty()); + QVERIFY(watcher.contains(tmpFile)); + + CloseHandle(h); + QVERIFY(!FileSystem::isFileLocked(tmpFile)); + + QThread::msleep(120); + qApp->processEvents(); + + QCOMPARE(count, 1); + QCOMPARE(file, tmpFile); + QVERIFY(!watcher.contains(tmpFile)); +#endif + QFile::remove(tmpFile); + } + +#ifdef Q_OS_WIN + void testLockedFilePropagation() + { + FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + + QStringList seenLockedFiles; + connect(&fakeFolder.syncEngine(), &SyncEngine::seenLockedFile, &fakeFolder.syncEngine(), + [&](const QString &file) { seenLockedFiles.append(file); }); + + LocalDiscoveryTracker tracker; + connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); + connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); + auto hasLocalDiscoveryPath = [&](const QString &path) { + auto &paths = tracker.localDiscoveryPaths(); + return paths.find(path.toUtf8()) != paths.end(); + }; + + // + // Local change, attempted upload, but file is locked! + // + fakeFolder.localModifier().appendByte("A/a1"); + tracker.addTouchedPath("A/a1"); + auto h1 = makeHandle(fakeFolder.localPath() + "A/a1", 0); + + fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + tracker.startSyncPartialDiscovery(); + QVERIFY(!fakeFolder.syncOnce()); + + QVERIFY(seenLockedFiles.contains(fakeFolder.localPath() + "A/a1")); + QVERIFY(seenLockedFiles.size() == 1); + QVERIFY(hasLocalDiscoveryPath("A/a1")); + + CloseHandle(h1); + + fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + tracker.startSyncPartialDiscovery(); + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + seenLockedFiles.clear(); + QVERIFY(tracker.localDiscoveryPaths().empty()); + + // + // Remote change, attempted download, but file is locked! + // + fakeFolder.remoteModifier().appendByte("A/a1"); + auto h2 = makeHandle(fakeFolder.localPath() + "A/a1", 0); + + fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + tracker.startSyncPartialDiscovery(); + QVERIFY(!fakeFolder.syncOnce()); + + QVERIFY(seenLockedFiles.contains(fakeFolder.localPath() + "A/a1")); + QVERIFY(seenLockedFiles.size() == 1); + + CloseHandle(h2); + + fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + tracker.startSyncPartialDiscovery(); + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } +#endif +}; + +QTEST_GUILESS_MAIN(TestLockedFiles) +#include "testlockedfiles.moc"