File indexing completed on 2024-04-28 11:35:08

0001 /*
0002     SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@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 <QFileInfo>
0008 #include <QTest>
0009 
0010 #include "kcoreaddons_debug.h"
0011 #include <kpluginloader.h>
0012 #include <kpluginmetadata.h>
0013 
0014 class LibraryPathRestorer
0015 {
0016 public:
0017     explicit LibraryPathRestorer(const QStringList &paths)
0018         : mPaths(paths)
0019     {
0020     }
0021     ~LibraryPathRestorer()
0022     {
0023         QCoreApplication::setLibraryPaths(mPaths);
0024     }
0025 
0026 private:
0027     QStringList mPaths;
0028 };
0029 
0030 class KPluginLoaderTest : public QObject
0031 {
0032     Q_OBJECT
0033 
0034 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 86)
0035 private Q_SLOTS:
0036     void testFindPlugin_missing()
0037     {
0038         const QString location = KPluginLoader::findPlugin(QStringLiteral("idonotexist"));
0039         QVERIFY2(location.isEmpty(), qPrintable(location));
0040     }
0041 
0042     void testFindPlugin()
0043     {
0044         const QString location = KPluginLoader::findPlugin(QStringLiteral("jsonplugin"));
0045         QVERIFY2(!location.isEmpty(), qPrintable(location));
0046     }
0047 
0048 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 84)
0049     void testPluginVersion()
0050     {
0051         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
0052         QCOMPARE(vplugin.pluginVersion(), quint32(5));
0053 
0054         KPluginLoader vplugin2(QStringLiteral("versionedplugin"));
0055         QCOMPARE(vplugin2.pluginVersion(), quint32(5));
0056 
0057         KPluginLoader uplugin(QStringLiteral("unversionedplugin"));
0058         QCOMPARE(uplugin.pluginVersion(), quint32(-1));
0059 
0060         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
0061         QCOMPARE(jplugin.pluginVersion(), quint32(-1));
0062 
0063         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
0064         QCOMPARE(eplugin.pluginVersion(), quint32(-1));
0065 
0066         KPluginLoader noplugin(QStringLiteral("idonotexist"));
0067         QCOMPARE(noplugin.pluginVersion(), quint32(-1));
0068     }
0069 #endif
0070 
0071     void testPluginName()
0072     {
0073         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
0074         QCOMPARE(vplugin.pluginName(), QString::fromLatin1("versionedplugin"));
0075 
0076         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
0077         QCOMPARE(jplugin.pluginName(), QString::fromLatin1("jsonplugin"));
0078 
0079         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
0080         QVERIFY2(eplugin.pluginName().isEmpty(), qPrintable(eplugin.pluginName()));
0081 
0082         KPluginLoader noplugin(QStringLiteral("idonotexist"));
0083         QCOMPARE(noplugin.pluginName(), QString::fromLatin1("idonotexist"));
0084     }
0085 
0086     void testFactory()
0087     {
0088         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
0089         QVERIFY(vplugin.factory());
0090 
0091         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
0092         QVERIFY(jplugin.factory());
0093 
0094         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
0095         QVERIFY(!eplugin.factory());
0096 
0097         KPluginLoader noplugin(QStringLiteral("idonotexist"));
0098         QVERIFY(!noplugin.factory());
0099     }
0100 
0101     void testErrorString()
0102     {
0103         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
0104         QCOMPARE(eplugin.errorString(), QString::fromLatin1("there was an error"));
0105     }
0106 
0107     void testFileName()
0108     {
0109         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
0110         QCOMPARE(QFileInfo(vplugin.fileName()).canonicalFilePath(), QFileInfo(QStringLiteral(VERSIONEDPLUGIN_FILE)).canonicalFilePath());
0111 
0112         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
0113         QCOMPARE(QFileInfo(jplugin.fileName()).canonicalFilePath(), QFileInfo(QStringLiteral(JSONPLUGIN_FILE)).canonicalFilePath());
0114 
0115         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
0116         QVERIFY2(eplugin.fileName().isEmpty(), qPrintable(eplugin.fileName()));
0117 
0118         KPluginLoader noplugin(QStringLiteral("idonotexist"));
0119         QVERIFY2(noplugin.fileName().isEmpty(), qPrintable(noplugin.fileName()));
0120     }
0121 
0122     void testInstance()
0123     {
0124         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
0125         QVERIFY(vplugin.instance());
0126 
0127         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
0128         QVERIFY(jplugin.instance());
0129 
0130         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
0131         QVERIFY(!eplugin.instance());
0132 
0133         KPluginLoader noplugin(QStringLiteral("idonotexist"));
0134         QVERIFY(!noplugin.instance());
0135     }
0136 
0137     void testIsLoaded()
0138     {
0139         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
0140         QVERIFY(!vplugin.isLoaded());
0141         QVERIFY(vplugin.load());
0142         QVERIFY(vplugin.isLoaded());
0143 
0144         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
0145         QVERIFY(!jplugin.isLoaded());
0146         QVERIFY(jplugin.load());
0147         QVERIFY(jplugin.isLoaded());
0148 
0149         KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
0150         QVERIFY(!aplugin.isLoaded());
0151         QVERIFY(aplugin.load());
0152         QVERIFY(aplugin.isLoaded());
0153         if (aplugin.unload()) {
0154             QVERIFY(!aplugin.isLoaded());
0155         } else {
0156             qCDebug(KCOREADDONS_DEBUG) << "Could not unload alwaysunloadplugin:" << aplugin.errorString();
0157         }
0158 
0159         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
0160         QVERIFY(!eplugin.isLoaded());
0161         QVERIFY(!eplugin.load());
0162         QVERIFY(!eplugin.isLoaded());
0163 
0164         KPluginLoader noplugin(QStringLiteral("idonotexist"));
0165         QVERIFY(!noplugin.isLoaded());
0166         QVERIFY(!noplugin.load());
0167         QVERIFY(!noplugin.isLoaded());
0168     }
0169 
0170     void testLoad()
0171     {
0172         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
0173         QVERIFY(vplugin.load());
0174 
0175         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
0176         QVERIFY(jplugin.load());
0177 
0178         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
0179         QVERIFY(!eplugin.load());
0180 
0181         KPluginLoader noplugin(QStringLiteral("idonotexist"));
0182         QVERIFY(!noplugin.load());
0183     }
0184 
0185     void testLoadHints()
0186     {
0187         KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
0188         QCOMPARE(aplugin.loadHints(), QLibrary::PreventUnloadHint);
0189         aplugin.setLoadHints(QLibrary::ResolveAllSymbolsHint);
0190         // setLoadHints merges in this scenario in the patch collection [1] but not in raw Qt5
0191         // [1] https://invent.kde.org/qt/qt/qtbase/-/merge_requests/285
0192         QVERIFY(aplugin.loadHints() == (QLibrary::ResolveAllSymbolsHint | QLibrary::PreventUnloadHint)
0193                 || aplugin.loadHints() == QLibrary::ResolveAllSymbolsHint);
0194     }
0195 
0196     void testMetaData()
0197     {
0198         KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
0199         QJsonObject ametadata = aplugin.metaData();
0200         QVERIFY(!ametadata.isEmpty());
0201         QVERIFY(ametadata.keys().contains(QLatin1String("IID")));
0202         QJsonValue ametadata_metadata = ametadata.value(QStringLiteral("MetaData"));
0203         QVERIFY(ametadata_metadata.toObject().isEmpty());
0204         QVERIFY(!aplugin.isLoaded()); // didn't load anything
0205 
0206         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
0207         QJsonObject jmetadata = jplugin.metaData();
0208         QVERIFY(!jmetadata.isEmpty());
0209         QJsonValue jmetadata_metadata = jmetadata.value(QStringLiteral("MetaData"));
0210         QVERIFY(jmetadata_metadata.isObject());
0211         QJsonObject jmetadata_obj = jmetadata_metadata.toObject();
0212         QVERIFY(!jmetadata_obj.isEmpty());
0213         QJsonValue comment = jmetadata_obj.value(QStringLiteral("KPlugin")).toObject().value(QStringLiteral("Description"));
0214         QVERIFY(comment.isString());
0215         QCOMPARE(comment.toString(), QString::fromLatin1("This is a plugin"));
0216 
0217         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
0218         QVERIFY(eplugin.metaData().isEmpty());
0219 
0220         KPluginLoader noplugin(QStringLiteral("idonotexist"));
0221         QVERIFY(noplugin.metaData().isEmpty());
0222     }
0223 
0224     void testUnload()
0225     {
0226         KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
0227         QVERIFY(aplugin.load());
0228         // may need QEXPECT_FAIL on some platforms...
0229         QVERIFY(aplugin.unload());
0230     }
0231 
0232     void testInstantiatePlugins()
0233     {
0234         const QString plugin1Path = KPluginLoader::findPlugin(QStringLiteral("jsonplugin"));
0235         QVERIFY2(!plugin1Path.isEmpty(), qPrintable(plugin1Path));
0236         const QString plugin2Path = KPluginLoader::findPlugin(QStringLiteral("unversionedplugin"));
0237         QVERIFY2(!plugin2Path.isEmpty(), qPrintable(plugin2Path));
0238         const QString plugin3Path = KPluginLoader::findPlugin(QStringLiteral("jsonplugin2"));
0239         QVERIFY2(!plugin3Path.isEmpty(), qPrintable(plugin3Path));
0240 
0241         QTemporaryDir temp;
0242         QVERIFY(temp.isValid());
0243         QDir dir(temp.path());
0244         QVERIFY2(QFile::copy(plugin1Path, dir.absoluteFilePath(QFileInfo(plugin1Path).fileName())),
0245                  qPrintable(dir.absoluteFilePath(QFileInfo(plugin1Path).fileName())));
0246         QVERIFY2(QFile::copy(plugin2Path, dir.absoluteFilePath(QFileInfo(plugin2Path).fileName())),
0247                  qPrintable(dir.absoluteFilePath(QFileInfo(plugin2Path).fileName())));
0248         QVERIFY2(QFile::copy(plugin3Path, dir.absoluteFilePath(QFileInfo(plugin3Path).fileName())),
0249                  qPrintable(dir.absoluteFilePath(QFileInfo(plugin3Path).fileName())));
0250 
0251         // only jsonplugin, since unversionedplugin has no json metadata
0252         QList<QObject *> plugins = KPluginLoader::instantiatePlugins(temp.path());
0253         QCOMPARE(plugins.size(), 2);
0254         QStringList classNames = QStringList() << QString::fromLatin1(plugins[0]->metaObject()->className())
0255                                                << QString::fromLatin1(plugins[1]->metaObject()->className());
0256         classNames.sort();
0257         QCOMPARE(classNames[0], QStringLiteral("jsonplugin2"));
0258         QCOMPARE(classNames[1], QStringLiteral("jsonpluginfa"));
0259         qDeleteAll(plugins);
0260 
0261         // try filter
0262         plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData &md) {
0263             return md.pluginId() == QLatin1String("jsonplugin");
0264         });
0265         QCOMPARE(plugins.size(), 1);
0266         QCOMPARE(plugins[0]->metaObject()->className(), "jsonpluginfa");
0267         qDeleteAll(plugins);
0268 
0269         plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData &md) {
0270             return md.pluginId() == QLatin1String("unversionedplugin");
0271         });
0272         QCOMPARE(plugins.size(), 0);
0273 
0274         plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData &md) {
0275             return md.pluginId() == QLatin1String("foobar"); // ID does not match file name, is set in JSON
0276         });
0277         QCOMPARE(plugins.size(), 1);
0278         QCOMPARE(plugins[0]->metaObject()->className(), "jsonplugin2");
0279         qDeleteAll(plugins);
0280 
0281         // check that parent gets set
0282         plugins = KPluginLoader::instantiatePlugins(
0283             temp.path(),
0284             [](const KPluginMetaData &) {
0285                 return true;
0286             },
0287             this);
0288         QCOMPARE(plugins.size(), 2);
0289         QCOMPARE(plugins[0]->parent(), this);
0290         QCOMPARE(plugins[1]->parent(), this);
0291         qDeleteAll(plugins);
0292 
0293         const QString subDirName = dir.dirName();
0294         QVERIFY(dir.cdUp()); // should now point to /tmp on Linux
0295         LibraryPathRestorer restorer(QCoreApplication::libraryPaths());
0296         // instantiate using relative path
0297         // make sure library path is set up correctly
0298         QCoreApplication::setLibraryPaths(QStringList() << dir.absolutePath());
0299         QVERIFY(!QDir::isAbsolutePath(subDirName));
0300         plugins = KPluginLoader::instantiatePlugins(subDirName);
0301         QCOMPARE(plugins.size(), 2);
0302         classNames = QStringList() << QString::fromLatin1(plugins[0]->metaObject()->className()) << QString::fromLatin1(plugins[1]->metaObject()->className());
0303         classNames.sort();
0304         QCOMPARE(classNames[0], QStringLiteral("jsonplugin2"));
0305         QCOMPARE(classNames[1], QStringLiteral("jsonpluginfa"));
0306         qDeleteAll(plugins);
0307     }
0308 
0309     void testForEachPlugin()
0310     {
0311         const QString jsonPluginSrc = KPluginLoader::findPlugin(QStringLiteral("jsonplugin"));
0312         QVERIFY2(!jsonPluginSrc.isEmpty(), qPrintable(jsonPluginSrc));
0313         const QString unversionedPluginSrc = KPluginLoader::findPlugin(QStringLiteral("unversionedplugin"));
0314         QVERIFY2(!unversionedPluginSrc.isEmpty(), qPrintable(unversionedPluginSrc));
0315         const QString jsonPlugin2Src = KPluginLoader::findPlugin(QStringLiteral("jsonplugin2"));
0316         QVERIFY2(!jsonPlugin2Src.isEmpty(), qPrintable(jsonPlugin2Src));
0317 
0318         QTemporaryDir temp;
0319         QVERIFY(temp.isValid());
0320         QDir dir(temp.path());
0321         QVERIFY(dir.mkdir(QStringLiteral("for-each-plugin")));
0322         QVERIFY(dir.cd(QStringLiteral("for-each-plugin")));
0323         const QString jsonPluginDest = dir.absoluteFilePath(QFileInfo(jsonPluginSrc).fileName());
0324         QVERIFY2(QFile::copy(jsonPluginSrc, jsonPluginDest), qPrintable(jsonPluginDest));
0325         const QString unversionedPluginDest = dir.absoluteFilePath(QFileInfo(unversionedPluginSrc).fileName());
0326         QVERIFY2(QFile::copy(unversionedPluginSrc, unversionedPluginDest), qPrintable(unversionedPluginDest));
0327         // copy jsonplugin2 to a "for-each-plugin" subdirectory in a different directory
0328         QTemporaryDir temp2;
0329         QVERIFY(temp2.isValid());
0330         QDir dir2(temp2.path());
0331         QVERIFY(dir2.mkdir(QStringLiteral("for-each-plugin")));
0332         QVERIFY(dir2.cd(QStringLiteral("for-each-plugin")));
0333         const QString jsonPlugin2Dest = dir2.absoluteFilePath(QFileInfo(jsonPlugin2Src).fileName());
0334         QVERIFY2(QFile::copy(jsonPlugin2Src, jsonPlugin2Dest), qPrintable(jsonPlugin2Dest));
0335 
0336         QStringList foundPlugins;
0337         QStringList expectedPlugins;
0338         const auto addToFoundPlugins = [&](const QString &path) {
0339             QVERIFY(!path.isEmpty());
0340             foundPlugins.append(path);
0341         };
0342 
0343         // test finding with absolute path
0344         expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest;
0345         expectedPlugins.sort();
0346         KPluginLoader::forEachPlugin(dir.path(), addToFoundPlugins);
0347         foundPlugins.sort();
0348         QCOMPARE(foundPlugins, expectedPlugins);
0349 
0350         expectedPlugins = QStringList() << jsonPlugin2Dest;
0351         expectedPlugins.sort();
0352         foundPlugins.clear();
0353         KPluginLoader::forEachPlugin(dir2.path(), addToFoundPlugins);
0354         foundPlugins.sort();
0355         QCOMPARE(foundPlugins, expectedPlugins);
0356 
0357         // now test relative paths
0358 
0359         LibraryPathRestorer restorer(QCoreApplication::libraryPaths());
0360         QCoreApplication::setLibraryPaths(QStringList() << temp.path());
0361         expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest;
0362         expectedPlugins.sort();
0363         foundPlugins.clear();
0364         KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins);
0365         foundPlugins.sort();
0366         QCOMPARE(foundPlugins, expectedPlugins);
0367 
0368         QCoreApplication::setLibraryPaths(QStringList() << temp2.path());
0369         expectedPlugins = QStringList() << jsonPlugin2Dest;
0370         expectedPlugins.sort();
0371         foundPlugins.clear();
0372         KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins);
0373         foundPlugins.sort();
0374         QCOMPARE(foundPlugins, expectedPlugins);
0375 
0376         QCoreApplication::setLibraryPaths(QStringList() << temp.path() << temp2.path());
0377         expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest << jsonPlugin2Dest;
0378         expectedPlugins.sort();
0379         foundPlugins.clear();
0380         KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins);
0381         foundPlugins.sort();
0382         QCOMPARE(foundPlugins, expectedPlugins);
0383     }
0384 #endif
0385 };
0386 
0387 QTEST_MAIN(KPluginLoaderTest)
0388 
0389 #include "kpluginloadertest.moc"