File indexing completed on 2024-10-13 03:42:25

0001 /*
0002     SPDX-FileCopyrightText: 2008 Kevin Ottens <ervin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include <QObject>
0008 #include <QThread>
0009 #include <QThreadPool>
0010 #include <qtconcurrentrun.h>
0011 
0012 #include <QTest>
0013 
0014 #include <solid/device.h>
0015 #include <solid/genericinterface.h>
0016 #include <solid/predicate.h>
0017 #include <solid/storagedrive.h>
0018 #include <solid/storagevolume.h>
0019 
0020 class SolidMtTest : public QObject
0021 {
0022     Q_OBJECT
0023 private Q_SLOTS:
0024     void testWorkerThread();
0025     void testThreadedPredicate();
0026     void testTextPredicates_data();
0027     void testTextPredicates();
0028     void testTextPredicatesExtended();
0029     void testDeviceMatching();
0030 };
0031 
0032 class WorkerThread : public QThread
0033 {
0034     Q_OBJECT
0035 protected:
0036     void run() override
0037     {
0038         Solid::Device dev("/org/freedesktop/Hal/devices/computer");
0039 
0040         const QList<Solid::Device> driveList = Solid::Device::listFromType(Solid::DeviceInterface::StorageDrive);
0041         for (const Solid::Device &solidDevice : driveList) {
0042             const Solid::StorageDrive *solidDrive = solidDevice.as<Solid::StorageDrive>();
0043             Q_ASSERT(solidDrive);
0044             Q_UNUSED(solidDrive);
0045         }
0046     }
0047 };
0048 
0049 static void doPredicates()
0050 {
0051     Solid::Predicate p5 =
0052         Solid::Predicate::fromString("[[Processor.maxSpeed == 3201 AND Processor.canChangeFrequency == false] OR StorageVolume.mountPoint == '/media/blup']");
0053 
0054     Solid::Predicate p6 = Solid::Predicate::fromString("StorageVolume.usage == 'Other'");
0055     Solid::Predicate p7 = Solid::Predicate::fromString(QString("StorageVolume.usage == %1").arg((int)Solid::StorageVolume::Other));
0056     Solid::Predicate p8 = Solid::Predicate::fromString("StorageVolume.ignored == false");
0057 }
0058 
0059 QTEST_MAIN(SolidMtTest)
0060 
0061 void SolidMtTest::testWorkerThread()
0062 {
0063     Solid::Device dev("/org/freedesktop/Hal/devices/acpi_ADP1");
0064 
0065     WorkerThread *wt = new WorkerThread;
0066     wt->start();
0067     wt->wait();
0068 
0069     const QList<Solid::Device> driveList = Solid::Device::listFromType(Solid::DeviceInterface::StorageDrive);
0070     for (const Solid::Device &solidDevice : driveList) {
0071         const Solid::GenericInterface *solidDrive = solidDevice.as<Solid::GenericInterface>();
0072         Q_ASSERT(solidDrive);
0073         Q_UNUSED(solidDrive);
0074     }
0075 
0076     delete wt;
0077 }
0078 
0079 void SolidMtTest::testThreadedPredicate()
0080 {
0081     QThreadPool::globalInstance()->setMaxThreadCount(10);
0082     const QList<QFuture<void>> futures = {
0083         QtConcurrent::run(&doPredicates),
0084         QtConcurrent::run(&doPredicates),
0085         QtConcurrent::run(&doPredicates),
0086         QtConcurrent::run(&doPredicates),
0087         QtConcurrent::run(&doPredicates),
0088         QtConcurrent::run(&doPredicates),
0089         QtConcurrent::run(&doPredicates),
0090         QtConcurrent::run(&doPredicates),
0091     };
0092     for (QFuture<void> f : futures) {
0093         f.waitForFinished();
0094     }
0095     QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads
0096 }
0097 
0098 
0099 void SolidMtTest::testTextPredicates_data()
0100 {
0101     QTest::addColumn<QString>("pstring");
0102     QTest::addColumn<bool>("isValidPredicate");
0103 
0104     QTest::newRow("empty") << QString() << false;
0105     QTest::newRow("simple") << QStringLiteral("@") << true;
0106     QTest::newRow("and") << QStringLiteral("@ AND @") << false;
0107     QTest::newRow("[and]") << QStringLiteral("[@ AND @]") << true;
0108     QTest::newRow("[3and]") << QStringLiteral("[@ AND @ AND @]") << false;
0109     QTest::newRow("[[and]and]") << QStringLiteral("[[@ AND @] AND @]") << true;
0110     QTest::newRow("[and[and]]") << QStringLiteral("[@ AND [@ AND @]]") << true;
0111     QTest::newRow("[[and][and] (unbalance)") << QStringLiteral("[ [@ AND @] AND [@ AND @]") << false;
0112     QTest::newRow("[[and][and]]") << QStringLiteral("[ [@ AND @] AND [@ AND @] ]") << true;
0113     QTest::newRow("[[[and]and]and]") << QStringLiteral("[ [ [ @ AND @] AND @ ] AND @]") << true;
0114 }
0115 
0116 /** @brief Check validity (and hence, parser-code) for a bunch of text predicates
0117  *
0118  */
0119 void SolidMtTest::testTextPredicates()
0120 {
0121     QFETCH(QString, pstring);
0122     QFETCH(bool, isValidPredicate);
0123 
0124     // The expressions contain @ as a placeholder, so they are
0125     // short enough to easily see the structure of the expression
0126     // in _data(); replace all those @s with an actual atom.
0127     const QString atom("StorageVolume.ignored == false");
0128     while(pstring.contains('@'))
0129     {
0130         pstring = pstring.replace('@', atom);
0131     }
0132 
0133     Solid::Predicate p = Solid::Predicate::fromString(pstring);
0134     QCOMPARE(p.isValid(), isValidPredicate);
0135 }
0136 
0137 void SolidMtTest::testTextPredicatesExtended()
0138 {
0139     Solid::Predicate p = Solid::Predicate::fromString("[[StorageVolume.ignored == false AND StorageVolume.usage == 'FileSystem'] AND [StorageVolume.ignored == false AND StorageDrive.removable == true]]");
0140     QVERIFY(p.isValid());
0141     QCOMPARE(p.type(), Solid::Predicate::Conjunction);
0142     QCOMPARE(p.firstOperand().type(), Solid::Predicate::Conjunction);
0143     QCOMPARE(p.firstOperand().firstOperand().type(), Solid::Predicate::PropertyCheck);
0144     QCOMPARE(p.secondOperand().secondOperand().type(), Solid::Predicate::PropertyCheck);
0145 
0146     QCOMPARE(p.firstOperand().firstOperand().propertyName(), QStringLiteral("ignored"));
0147     QCOMPARE(p.firstOperand().firstOperand().interfaceType(), Solid::DeviceInterface::StorageVolume);
0148     // Let's not call firstOperand() quite so much, and copy into a local atom
0149     {
0150         auto atom = p.firstOperand().firstOperand();
0151         QVERIFY(atom.isValid());
0152         QCOMPARE(atom.propertyName(), QStringLiteral("ignored"));
0153         QCOMPARE(atom.matchingValue().userType(), QMetaType::Bool);
0154         QCOMPARE(atom.matchingValue().toBool(), false);
0155     }
0156     {
0157         auto atom = p.firstOperand().secondOperand();
0158         QVERIFY(atom.isValid());
0159         QCOMPARE(atom.interfaceType(), Solid::DeviceInterface::StorageVolume);
0160         QCOMPARE(atom.propertyName(), QStringLiteral("usage"));
0161         QCOMPARE(atom.matchingValue().userType(), QMetaType::QString);
0162         QCOMPARE(atom.matchingValue().toString(), QStringLiteral("FileSystem"));
0163     }
0164     {
0165         auto atom = p.secondOperand().firstOperand();
0166         QVERIFY(atom.isValid());
0167         QCOMPARE(atom.interfaceType(), Solid::DeviceInterface::StorageVolume);
0168         QCOMPARE(atom.propertyName(), QStringLiteral("ignored"));
0169         QCOMPARE(atom.matchingValue().userType(), QMetaType::Bool);
0170         QCOMPARE(atom.matchingValue().toBool(), false);
0171     }
0172     {
0173         auto atom = p.secondOperand().secondOperand();
0174         QVERIFY(atom.isValid());
0175         QCOMPARE(atom.interfaceType(), Solid::DeviceInterface::StorageDrive);
0176         QCOMPARE(atom.propertyName(), QStringLiteral("removable"));
0177         QCOMPARE(atom.matchingValue().userType(), QMetaType::Bool);
0178         QCOMPARE(atom.matchingValue().toBool(), true);
0179     }
0180 }
0181 
0182 void SolidMtTest::testDeviceMatching()
0183 {
0184     auto deviceList = Solid::Device::allDevices();
0185     QVERIFY(!deviceList.isEmpty());
0186 
0187     Solid::Predicate volumesPredicate = Solid::Predicate::fromString("StorageVolume.ignored == false");
0188     QVERIFY(volumesPredicate.isValid());
0189 
0190     int volumes = 0;
0191     for (const auto &device : std::as_const(deviceList)) {
0192         if(volumesPredicate.matches(device))
0193         {
0194             volumes++;
0195         }
0196     }
0197     // If there are no removable volumes, that's fine; below we will be matching empty lists.
0198     // QVERIFY(volumes > 0);
0199 
0200     // From the pre-parsed query
0201     {
0202         auto matchedDeviceList = Solid::Device::listFromQuery(volumesPredicate);
0203         QCOMPARE(matchedDeviceList.size(), volumes);
0204     }
0205     // Same from text again
0206     {
0207         auto matchedDeviceList = Solid::Device::listFromQuery("StorageVolume.ignored == false");
0208         QCOMPARE(matchedDeviceList.size(), volumes);
0209     }
0210 
0211     // Are there removables? Hope so.
0212     //
0213     // The removableFSVolumesPredicate is checking two incompatible things:
0214     // a given Solid::Device isn't a Volume and a Drive at the same time,
0215     // so it cannot match.
0216     {
0217         Solid::Predicate fsVolumesPredicate = Solid::Predicate::fromString("[StorageVolume.ignored == false AND StorageVolume.usage == 'FileSystem']");
0218         Solid::Predicate removablePredicate = Solid::Predicate::fromString("StorageDrive.removable == true");
0219         Solid::Predicate removableFSVolumesPredicate = Solid::Predicate::fromString("[[StorageVolume.ignored == false AND StorageVolume.usage == 'FileSystem'] AND StorageDrive.removable == true]");
0220         volumes = 0;
0221         for (const auto &device : std::as_const(deviceList)) {
0222             qDebug() << device.displayName() << "fs?" << fsVolumesPredicate.matches(device) << "removable?" << removablePredicate.matches(device) << "removableFS?" << removableFSVolumesPredicate.matches(device);
0223             if (removableFSVolumesPredicate.matches(device))
0224             {
0225                 volumes++;
0226             }
0227         }
0228         QCOMPARE(volumes, 0);
0229 
0230         auto matchedDeviceList = Solid::Device::listFromQuery(removableFSVolumesPredicate);
0231         QCOMPARE(matchedDeviceList.size(), volumes);
0232     }
0233 
0234     // Internally, the predicate is going to check interfaces,
0235     // so let's do that here as well. This will show no device is
0236     // both a Volume and a Drive.
0237     {
0238         const Solid::DeviceInterface::Type storageVolume = Solid::DeviceInterface::Type::StorageVolume;
0239         const Solid::DeviceInterface::Type storageDrive = Solid::DeviceInterface::Type::StorageDrive;
0240 
0241         int volumeCount = 0;
0242         int driveCount = 0;
0243         int bothCount = 0;
0244 
0245         for (const auto &device : std::as_const(deviceList)) {
0246             // Copied from internals in predicate.cpp:
0247             //        const DeviceInterface *iface = device.asDeviceInterface(d->ifaceType);
0248             const Solid::DeviceInterface *volumeIface = device.asDeviceInterface(storageVolume);
0249             const Solid::DeviceInterface *driveIface = device.asDeviceInterface(storageDrive);
0250             volumeCount += volumeIface ? 1 : 0;
0251             driveCount += driveIface ? 1 : 0;
0252             bothCount += (volumeIface && driveIface) ? 1 : 0;
0253         }
0254         QCOMPARE(bothCount, 0);
0255         if (volumeCount + driveCount > 0)
0256         {
0257             // On Linux CI, there is no DBus connection, so no drives or volumes at all.
0258             // On a local machine, or on FreeBSD CI, there are volumes and/or drives,
0259             // so verify that there's at least one of each.
0260             QVERIFY(volumeCount > 0);
0261             QVERIFY(driveCount > 0);
0262         }
0263     }
0264 }
0265 
0266 #include "solidmttest.moc"