[system/kio-snapshot] /: Add autotests

Bharadwaj Raju <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 2e80d0d96cbbccef4297a31cc93c3bae960db776 by Bharadwaj Raju.
Committed on 27/07/2026 at 14:34.
Pushed by bharadwaj-raju into branch 'master'.

Add autotests

TODO add them to CI using KDE Linux's image

M  +4    -0    CMakeLists.txt
A  +10   -0    autotests/CMakeLists.txt
A  +8    -0    autotests/README.md
A  +39   -0    autotests/prep_btrfs.sh
A  +128  -0    autotests/test_filesnapshots_worker.cpp  *
A  +109  -0    autotests/test_snapshot_worker.cpp  *

The files marked with a * at the end have a non valid license. Please read: https://community.kde.org/Policies/Licensing_Policy and use the headers which are listed at that page.


https://invent.kde.org/system/kio-snapshot/-/commit/2e80d0d96cbbccef4297a31cc93c3bae960db776

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c6c9416..829dc25 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,6 +27,7 @@ include(FeatureSummary)
 
 find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS
     Core
+    Test
 )
 
 find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
@@ -59,3 +60,6 @@ add_subdirectory(kioworker)
 add_subdirectory(contextmenu)
 
 install(FILES org.kde.kio_snapshot.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
+
+include(CTest)
+add_subdirectory(autotests)
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
new file mode 100644
index 0000000..a161288
--- /dev/null
+++ b/autotests/CMakeLists.txt
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: 2026 Bharadwaj Raju <[email protected]>
+# SPDX-License-Identifier: LGPL-2.0-or-later
+
+add_executable(SnapshotWorkerTest test_snapshot_worker.cpp)
+target_link_libraries(SnapshotWorkerTest PRIVATE Qt6::Core Qt6::Test KF6::KIOCore KF6::Solid)
+add_test(NAME SnapshotWorkerTest COMMAND SnapshotWorkerTest)
+
+add_executable(FileSnapshotsWorkerTest test_filesnapshots_worker.cpp)
+target_link_libraries(FileSnapshotsWorkerTest PRIVATE Qt6::Core Qt6::Test KF6::KIOCore)
+add_test(NAME FileSnapshotsWorkerTest COMMAND FileSnapshotsWorkerTest)
diff --git a/autotests/README.md b/autotests/README.md
new file mode 100644
index 0000000..1deabf7
--- /dev/null
+++ b/autotests/README.md
@@ -0,0 +1,8 @@
+# Testing kio-snapshot
+
+The testing relies on a Btrfs filesystem with snapshots.
+The included `prep_btrfs.sh` creates an empty 128 MB Btrfs filesystem image,
+mounts it at `butter-tray` in the working directory, and sets up test files
+and subvolumes and snapshots inside it.
+
+Please pass the path to the mountpoint as the environment variable `KIO_SNAPSHOT_TEST_MOUNTPOINT`.
diff --git a/autotests/prep_btrfs.sh b/autotests/prep_btrfs.sh
new file mode 100644
index 0000000..e9e5906
--- /dev/null
+++ b/autotests/prep_btrfs.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+
+# SPDX-FileCopyrightText: 2026 Bharadwaj Raju <[email protected]>
+# SPDX-License-Identifier: LGPL-2.0-or-later
+
+set -eux
+
+sudo umount butter-tray || true
+sudo rmdir butter-tray || true
+sudo rm butter || true
+
+truncate --size 128M butter  # 128M is the minimum size for Btrfs, apparently
+mkfs.btrfs butter
+sudo mount --mkdir --type=btrfs -o uhelper=udisks2 butter butter-tray  # we need udisks to see the mount, so Solid::storageAccessForPath can work
+sudo chown -R $USER:$USER butter-tray
+
+sudo btrfs subvolume snapshot butter-tray butter-tray/@initial
+echo "hello" > butter-tray/file.txt
+sudo btrfs subvolume snapshot butter-tray butter-tray/@after-creation
+sudo btrfs subvolume snapshot butter-tray butter-tray/@duplicate
+echo "world" >> butter-tray/file.txt
+sudo btrfs subvolume snapshot butter-tray butter-tray/@after-additions
+rm butter-tray/file.txt
+sudo btrfs subvolume snapshot butter-tray butter-tray/@after-removal
+echo "again" > butter-tray/file.txt
+sudo btrfs subvolume snapshot butter-tray butter-tray/@after-recreation
+
+sudo btrfs subvolume create butter-tray/sub
+sudo chown -R $USER:$USER butter-tray/sub
+sudo btrfs subvolume snapshot butter-tray/sub butter-tray/@sub-initial
+echo "hello from subvolume" > butter-tray/sub/vol.txt
+sudo btrfs subvolume snapshot butter-tray/sub butter-tray/@sub-after-creation
+sudo btrfs subvolume snapshot butter-tray/sub butter-tray/@sub-duplicate
+echo "world" >> butter-tray/sub/vol.txt
+sudo btrfs subvolume snapshot butter-tray/sub butter-tray/@sub-after-additions
+rm butter-tray/sub/vol.txt
+sudo btrfs subvolume snapshot butter-tray/sub butter-tray/@sub-after-removal
+echo "again" > butter-tray/sub/vol.txt
+sudo btrfs subvolume snapshot butter-tray/sub butter-tray/@sub-after-recreation
diff --git a/autotests/test_filesnapshots_worker.cpp b/autotests/test_filesnapshots_worker.cpp
new file mode 100644
index 0000000..19a2a03
--- /dev/null
+++ b/autotests/test_filesnapshots_worker.cpp
@@ -0,0 +1,128 @@
+#include <KIO/ListJob>
+
+#include <QDir>
+#include <QObject>
+#include <QString>
+#include <QtTest>
+
+using namespace Qt::StringLiterals;
+
+class TestFileSnapshotsWorker : public QObject
+{
+    Q_OBJECT
+
+private:
+    QString m_testMount;
+
+private Q_SLOTS:
+    void initTestCase()
+    {
+        m_testMount = QString::fromUtf8(qgetenv("KIO_SNAPSHOT_TEST_MOUNTPOINT"));
+        if (m_testMount.isEmpty() || !QDir(m_testMount).exists()) {
+            QFAIL("Could not access mountpoint for test Btrfs filesystem (check env var KIO_SNAPSHOT_TEST_MOUNTPOINT)");
+        }
+    };
+
+    void testRootFile()
+    {
+        QUrl url;
+        url.setScheme("filesnapshots"_L1);
+        url.setPath(QDir::cleanPath(m_testMount + "/file.txt"_L1));
+        KIO::ListJob *listJob = KIO::listDir(url, KIO::HideProgressInfo);
+        connect(listJob, &KIO::ListJob::entries, this, &TestFileSnapshotsWorker::slotRootFileEntries);
+        QVERIFY(listJob->exec());
+    };
+
+    void testSubvolumeFile()
+    {
+        QUrl url;
+        url.setScheme("filesnapshots"_L1);
+        url.setPath(QDir::cleanPath(m_testMount + "/sub/vol.txt"_L1));
+        KIO::ListJob *listJob = KIO::listDir(url, KIO::HideProgressInfo);
+        connect(listJob, &KIO::ListJob::entries, this, &TestFileSnapshotsWorker::slotSubvolumeFileEntries);
+        QVERIFY(listJob->exec());
+    };
+
+protected Q_SLOTS:
+    void slotRootFileEntries(KIO::Job *, const KIO::UDSEntryList &entries)
+    {
+        bool hasCurrent = false;
+        bool hasAfterCreation = false;
+        bool hasDuplicate = false;
+        bool hasAfterAdditions = false;
+        bool hasAfterRemoval = false;
+        bool hasAfterRecreation = false;
+        for (const KIO::UDSEntry &entry : std::as_const(entries)) {
+            qDebug() << entry;
+            const auto localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
+            QVERIFY(!localPath.isEmpty());
+            if (localPath == QDir::cleanPath(m_testMount + "/file.txt"_L1)) {
+                hasCurrent = true;
+            }
+            if (localPath.contains("@after-creation"_L1)) {
+                hasAfterCreation = true;
+            }
+            if (localPath.contains("@duplicate"_L1)) {
+                hasDuplicate = true;
+            }
+            if (localPath.contains("@after-additions"_L1)) {
+                hasAfterAdditions = true;
+            }
+            if (localPath.contains("@after-removal"_L1)) {
+                hasAfterRemoval = true;
+            }
+            if (localPath.contains("@after-recreation"_L1)) {
+                hasAfterRecreation = true;
+            }
+        }
+        QVERIFY(hasCurrent);
+        QVERIFY(hasAfterCreation);
+        QVERIFY(!hasDuplicate);
+        QVERIFY(hasAfterAdditions);
+        QVERIFY(!hasAfterRemoval);
+        QVERIFY(hasAfterRecreation);
+    }
+
+    void slotSubvolumeFileEntries(KIO::Job *, const KIO::UDSEntryList &entries)
+    {
+        bool hasCurrent = false;
+        bool hasAfterCreation = false;
+        bool hasDuplicate = false;
+        bool hasAfterAdditions = false;
+        bool hasAfterRemoval = false;
+        bool hasAfterRecreation = false;
+        for (const KIO::UDSEntry &entry : std::as_const(entries)) {
+            qDebug() << entry;
+            const auto localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
+            QVERIFY(!localPath.isEmpty());
+            if (localPath == QDir::cleanPath(m_testMount + "/sub/vol.txt"_L1)) {
+                hasCurrent = true;
+            }
+            if (localPath.contains("@sub-after-creation"_L1)) {
+                hasAfterCreation = true;
+            }
+            if (localPath.contains("@sub-duplicate"_L1)) {
+                hasDuplicate = true;
+            }
+            if (localPath.contains("@sub-after-additions"_L1)) {
+                hasAfterAdditions = true;
+            }
+            if (localPath.contains("@sub-after-removal"_L1)) {
+                hasAfterRemoval = true;
+            }
+            if (localPath.contains("@sub-after-recreation"_L1)) {
+                hasAfterRecreation = true;
+            }
+        }
+        QVERIFY(hasCurrent);
+        QVERIFY(hasAfterCreation);
+        QVERIFY(!hasDuplicate);
+        QVERIFY(hasAfterAdditions);
+        QVERIFY(!hasAfterRemoval);
+        QVERIFY(hasAfterRecreation);
+    }
+};
+
+QTEST_GUILESS_MAIN(TestFileSnapshotsWorker)
+
+#include "test_filesnapshots_worker.moc"
diff --git a/autotests/test_snapshot_worker.cpp b/autotests/test_snapshot_worker.cpp
new file mode 100644
index 0000000..ba16e88
--- /dev/null
+++ b/autotests/test_snapshot_worker.cpp
@@ -0,0 +1,109 @@
+#include <KIO/ListJob>
+
+#include <Solid/Device>
+#include <Solid/StorageAccess>
+#include <Solid/StorageVolume>
+
+#include <QDir>
+#include <QObject>
+#include <QString>
+#include <QTest>
+#include <QtTest>
+
+using namespace Qt::StringLiterals;
+
+class TestSnapshotWorker : public QObject
+{
+    Q_OBJECT
+
+private:
+    QString m_testMount;
+    QString m_fsUuid;
+    QUrl m_subSnapshotsUrl;
+
+private Q_SLOTS:
+    void initTestCase()
+    {
+        m_testMount = QString::fromUtf8(qgetenv("KIO_SNAPSHOT_TEST_MOUNTPOINT"));
+        if (m_testMount.isEmpty() || !QDir(m_testMount).exists()) {
+            QFAIL("Could not access mountpoint for test Btrfs filesystem (check env var KIO_SNAPSHOT_TEST_MOUNTPOINT)");
+        }
+        auto fsDevice = Solid::Device::storageAccessFromPath(m_testMount);
+        auto fsAccess = fsDevice.as<Solid::StorageAccess>();
+        if (!fsAccess) {
+            QFAIL("could not determine fs root path");
+        }
+        qDebug() << fsAccess->filePath();
+        QString fsRootPath = fsAccess->filePath();
+        auto fsVolume = fsDevice.as<Solid::StorageVolume>();
+        if (!fsVolume) {
+            QFAIL("could not determine fs storage volume for");
+        }
+        qDebug() << fsVolume->uuid();
+        m_fsUuid = fsVolume->uuid();
+    };
+
+    void testListAllSubvolumes()
+    {
+        QUrl url;
+        url.setScheme("snapshot"_L1);
+        url.setHost(m_fsUuid);
+        KIO::ListJob *listJob = KIO::listDir(url, KIO::HideProgressInfo);
+        connect(listJob, &KIO::ListJob::entries, this, &TestSnapshotWorker::slotAllSubvolumesEntries);
+        QVERIFY(listJob->exec());
+    };
+
+    void testSubvolume()
+    {
+        QUrl url;
+        url.setScheme("snapshot"_L1);
+        url.setHost(m_fsUuid);
+        KIO::ListJob *listJob = KIO::listDir(url, KIO::HideProgressInfo);
+        connect(listJob, &KIO::ListJob::entries, this, &TestSnapshotWorker::slotGetSubSnapshotsUrl);
+        QVERIFY(listJob->exec());
+    };
+
+protected Q_SLOTS:
+    void slotAllSubvolumesEntries(KIO::Job *, const KIO::UDSEntryList &entries)
+    {
+        bool hasRoot = false;
+        bool hasSub = false;
+        for (const KIO::UDSEntry &entry : std::as_const(entries)) {
+            qDebug() << entry;
+            const auto name = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
+            if (name.contains(QDir::cleanPath(m_testMount + "/sub"_L1))) {
+                hasSub = true;
+            }
+        }
+        QVERIFY(hasSub);
+        QEXPECT_FAIL("", "TODO get BtrfsSnapshots::getNonSnapshotSubvolumes to return root volume", Continue);
+        QVERIFY(hasRoot);
+        QEXPECT_FAIL("", "TODO get BtrfsSnapshots::getNonSnapshotSubvolumes to return root volume", Continue);
+        QCOMPARE(entries.size(), 2);
+    }
+
+    void slotGetSubSnapshotsUrl(KIO::Job *, const KIO::UDSEntryList &entries)
+    {
+        for (const KIO::UDSEntry &entry : std::as_const(entries)) {
+            qDebug() << "aaa" << entry;
+            const auto name = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
+            if (name.contains(QDir::cleanPath(m_testMount + "/sub"_L1))) {
+                m_subSnapshotsUrl = QUrl(entry.stringValue(KIO::UDSEntry::UDS_URL));
+                qDebug() << m_subSnapshotsUrl;
+            }
+        }
+
+        KIO::ListJob *subListJob = KIO::listDir(m_subSnapshotsUrl, KIO::HideProgressInfo);
+        connect(subListJob, &KIO::ListJob::entries, this, &TestSnapshotWorker::slotSubSnapshotEntries);
+        QVERIFY(subListJob->exec());
+    }
+
+    void slotSubSnapshotEntries(KIO::Job *, const KIO::UDSEntryList &entries)
+    {
+        QCOMPARE(entries.size(), 6);
+    }
+};
+
+QTEST_GUILESS_MAIN(TestSnapshotWorker)
+
+#include "test_snapshot_worker.moc"
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.