Test: Add test for locked file tracking and propagation
authorChristian Kamm <mail@ckamm.de>
Mon, 5 Nov 2018 11:12:49 +0000 (12:12 +0100)
committerKevin Ottens <kevin.ottens@nextcloud.com>
Tue, 15 Dec 2020 09:58:17 +0000 (10:58 +0100)
src/gui/lockwatcher.cpp
src/gui/lockwatcher.h
test/CMakeLists.txt
test/testlockedfiles.cpp [new file with mode: 0644]

index 3f8030cde105594825858cf3052bd9ec351ad734..d3edc23522416b5cbfb407f0b91eabd2078e8f2f 100644 (file)
@@ -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<QString> unlocked;
index 13b845e12eed5239b148c8d33d7836118601ebfb..f5e27bac617a2695bee8a90733285feaca3e8160 100644 (file)
@@ -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. */
index 1568c935427ac5ef8252b4ed622420566bfabca4..8c719097e0efffeabd280b3778fb9ce673926c26 100644 (file)
@@ -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 (file)
index 0000000..df1e8f8
--- /dev/null
@@ -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 <QtTest>
+#include "syncenginetestutils.h"
+#include "lockwatcher.h"
+#include <syncengine.h>
+#include <localdiscoverytracker.h>
+
+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<const wchar_t *>(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"