/* SPDX-FileCopyrightText: 2008 Kevin Ottens SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include #include #include #include #include #include #include #include #include #include class SolidMtTest : public QObject { Q_OBJECT private Q_SLOTS: void testWorkerThread(); void testThreadedPredicate(); void testTextPredicates_data(); void testTextPredicates(); void testTextPredicatesExtended(); void testDeviceMatching(); }; class WorkerThread : public QThread { Q_OBJECT protected: void run() override { Solid::Device dev(QStringLiteral("/org/freedesktop/Hal/devices/computer")); const QList driveList = Solid::Device::listFromType(Solid::DeviceInterface::StorageDrive); for (const Solid::Device &solidDevice : driveList) { const Solid::StorageDrive *solidDrive = solidDevice.as(); Q_ASSERT(solidDrive); Q_UNUSED(solidDrive); } } }; static void doPredicates() { Solid::Predicate p5 = Solid::Predicate::fromString( QStringLiteral("[[Processor.maxSpeed == 3201 AND Processor.canChangeFrequency == false] OR StorageVolume.mountPoint == '/media/blup']")); Solid::Predicate p6 = Solid::Predicate::fromString(QStringLiteral("StorageVolume.usage == 'Other'")); Solid::Predicate p7 = Solid::Predicate::fromString(QStringLiteral("StorageVolume.usage == %1").arg((int)Solid::StorageVolume::Other)); Solid::Predicate p8 = Solid::Predicate::fromString(QStringLiteral("StorageVolume.ignored == false")); } QTEST_MAIN(SolidMtTest) void SolidMtTest::testWorkerThread() { Solid::Device dev(QStringLiteral("/org/freedesktop/Hal/devices/acpi_ADP1")); WorkerThread *wt = new WorkerThread; wt->start(); wt->wait(); const QList driveList = Solid::Device::listFromType(Solid::DeviceInterface::StorageDrive); for (const Solid::Device &solidDevice : driveList) { const Solid::GenericInterface *solidDrive = solidDevice.as(); Q_ASSERT(solidDrive); Q_UNUSED(solidDrive); } delete wt; } void SolidMtTest::testThreadedPredicate() { QThreadPool::globalInstance()->setMaxThreadCount(10); const QList> futures = { QtConcurrent::run(&doPredicates), QtConcurrent::run(&doPredicates), QtConcurrent::run(&doPredicates), QtConcurrent::run(&doPredicates), QtConcurrent::run(&doPredicates), QtConcurrent::run(&doPredicates), QtConcurrent::run(&doPredicates), QtConcurrent::run(&doPredicates), }; for (QFuture f : futures) { f.waitForFinished(); } QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads } void SolidMtTest::testTextPredicates_data() { QTest::addColumn("pstring"); QTest::addColumn("isValidPredicate"); QTest::newRow("empty") << QString() << false; QTest::newRow("simple") << QStringLiteral("@") << true; QTest::newRow("and") << QStringLiteral("@ AND @") << false; QTest::newRow("[and]") << QStringLiteral("[@ AND @]") << true; QTest::newRow("[3and]") << QStringLiteral("[@ AND @ AND @]") << false; QTest::newRow("[[and]and]") << QStringLiteral("[[@ AND @] AND @]") << true; QTest::newRow("[and[and]]") << QStringLiteral("[@ AND [@ AND @]]") << true; QTest::newRow("[[and][and] (unbalance)") << QStringLiteral("[ [@ AND @] AND [@ AND @]") << false; QTest::newRow("[[and][and]]") << QStringLiteral("[ [@ AND @] AND [@ AND @] ]") << true; QTest::newRow("[[[and]and]and]") << QStringLiteral("[ [ [ @ AND @] AND @ ] AND @]") << true; } /** @brief Check validity (and hence, parser-code) for a bunch of text predicates * */ void SolidMtTest::testTextPredicates() { QFETCH(QString, pstring); QFETCH(bool, isValidPredicate); // The expressions contain @ as a placeholder, so they are // short enough to easily see the structure of the expression // in _data(); replace all those @s with an actual atom. const QString atom(QStringLiteral("StorageVolume.ignored == false")); while (pstring.contains(QLatin1Char('@'))) { pstring = pstring.replace(QLatin1Char('@'), atom); } Solid::Predicate p = Solid::Predicate::fromString(pstring); QCOMPARE(p.isValid(), isValidPredicate); } void SolidMtTest::testTextPredicatesExtended() { Solid::Predicate p = Solid::Predicate::fromString(QStringLiteral( "[[StorageVolume.ignored == false AND StorageVolume.usage == 'FileSystem'] AND [StorageVolume.ignored == false AND StorageDrive.removable == true]]")); QVERIFY(p.isValid()); QCOMPARE(p.type(), Solid::Predicate::Conjunction); QCOMPARE(p.firstOperand().type(), Solid::Predicate::Conjunction); QCOMPARE(p.firstOperand().firstOperand().type(), Solid::Predicate::PropertyCheck); QCOMPARE(p.secondOperand().secondOperand().type(), Solid::Predicate::PropertyCheck); QCOMPARE(p.firstOperand().firstOperand().propertyName(), QStringLiteral("ignored")); QCOMPARE(p.firstOperand().firstOperand().interfaceType(), Solid::DeviceInterface::StorageVolume); // Let's not call firstOperand() quite so much, and copy into a local atom { auto atom = p.firstOperand().firstOperand(); QVERIFY(atom.isValid()); QCOMPARE(atom.propertyName(), QStringLiteral("ignored")); QCOMPARE(atom.matchingValue().userType(), QMetaType::Bool); QCOMPARE(atom.matchingValue().toBool(), false); } { auto atom = p.firstOperand().secondOperand(); QVERIFY(atom.isValid()); QCOMPARE(atom.interfaceType(), Solid::DeviceInterface::StorageVolume); QCOMPARE(atom.propertyName(), QStringLiteral("usage")); QCOMPARE(atom.matchingValue().userType(), QMetaType::QString); QCOMPARE(atom.matchingValue().toString(), QStringLiteral("FileSystem")); } { auto atom = p.secondOperand().firstOperand(); QVERIFY(atom.isValid()); QCOMPARE(atom.interfaceType(), Solid::DeviceInterface::StorageVolume); QCOMPARE(atom.propertyName(), QStringLiteral("ignored")); QCOMPARE(atom.matchingValue().userType(), QMetaType::Bool); QCOMPARE(atom.matchingValue().toBool(), false); } { auto atom = p.secondOperand().secondOperand(); QVERIFY(atom.isValid()); QCOMPARE(atom.interfaceType(), Solid::DeviceInterface::StorageDrive); QCOMPARE(atom.propertyName(), QStringLiteral("removable")); QCOMPARE(atom.matchingValue().userType(), QMetaType::Bool); QCOMPARE(atom.matchingValue().toBool(), true); } } void SolidMtTest::testDeviceMatching() { auto deviceList = Solid::Device::allDevices(); QVERIFY(!deviceList.isEmpty()); Solid::Predicate volumesPredicate = Solid::Predicate::fromString(QStringLiteral("StorageVolume.ignored == false")); QVERIFY(volumesPredicate.isValid()); int volumes = 0; for (const auto &device : std::as_const(deviceList)) { if(volumesPredicate.matches(device)) { volumes++; } } // If there are no removable volumes, that's fine; below we will be matching empty lists. // QVERIFY(volumes > 0); // From the pre-parsed query { auto matchedDeviceList = Solid::Device::listFromQuery(volumesPredicate); QCOMPARE(matchedDeviceList.size(), volumes); } // Same from text again { auto matchedDeviceList = Solid::Device::listFromQuery(QStringLiteral("StorageVolume.ignored == false")); QCOMPARE(matchedDeviceList.size(), volumes); } // Are there removables? Hope so. // // The removableFSVolumesPredicate is checking two incompatible things: // a given Solid::Device isn't a Volume and a Drive at the same time, // so it cannot match. { Solid::Predicate fsVolumesPredicate = Solid::Predicate::fromString(QStringLiteral("[StorageVolume.ignored == false AND StorageVolume.usage == 'FileSystem']")); Solid::Predicate removablePredicate = Solid::Predicate::fromString(QStringLiteral("StorageDrive.removable == true")); Solid::Predicate removableFSVolumesPredicate = Solid::Predicate::fromString( QStringLiteral("[[StorageVolume.ignored == false AND StorageVolume.usage == 'FileSystem'] AND StorageDrive.removable == true]")); volumes = 0; for (const auto &device : std::as_const(deviceList)) { qDebug() << device.displayName() << "fs?" << fsVolumesPredicate.matches(device) << "removable?" << removablePredicate.matches(device) << "removableFS?" << removableFSVolumesPredicate.matches(device); if (removableFSVolumesPredicate.matches(device)) { volumes++; } } QCOMPARE(volumes, 0); auto matchedDeviceList = Solid::Device::listFromQuery(removableFSVolumesPredicate); QCOMPARE(matchedDeviceList.size(), volumes); } // Internally, the predicate is going to check interfaces, // so let's do that here as well. This will show no device is // both a Volume and a Drive. { const Solid::DeviceInterface::Type storageVolume = Solid::DeviceInterface::Type::StorageVolume; const Solid::DeviceInterface::Type storageDrive = Solid::DeviceInterface::Type::StorageDrive; int volumeCount = 0; int driveCount = 0; int bothCount = 0; for (const auto &device : std::as_const(deviceList)) { // Copied from internals in predicate.cpp: // const DeviceInterface *iface = device.asDeviceInterface(d->ifaceType); const Solid::DeviceInterface *volumeIface = device.asDeviceInterface(storageVolume); const Solid::DeviceInterface *driveIface = device.asDeviceInterface(storageDrive); volumeCount += volumeIface ? 1 : 0; driveCount += driveIface ? 1 : 0; bothCount += (volumeIface && driveIface) ? 1 : 0; } QCOMPARE(bothCount, 0); if (volumeCount + driveCount > 0) { // On Linux CI, there is no DBus connection, so no drives or volumes at all. // On a local machine, or on FreeBSD CI, there are volumes and/or drives, // so verify that there's at least one of each. QVERIFY(volumeCount > 0); QVERIFY(driveCount > 0); } } } #include "solidmttest.moc"