[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"