1 /*
2     SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 
7 #include <QFileInfo>
8 #include <QTest>
9 
10 #include "kcoreaddons_debug.h"
11 #include <kpluginloader.h>
12 #include <kpluginmetadata.h>
13 
14 class LibraryPathRestorer
15 {
16 public:
LibraryPathRestorer(const QStringList & paths)17     explicit LibraryPathRestorer(const QStringList &paths)
18         : mPaths(paths)
19     {
20     }
~LibraryPathRestorer()21     ~LibraryPathRestorer()
22     {
23         QCoreApplication::setLibraryPaths(mPaths);
24     }
25 
26 private:
27     QStringList mPaths;
28 };
29 
30 class KPluginLoaderTest : public QObject
31 {
32     Q_OBJECT
33 
34 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 86)
35 private Q_SLOTS:
testFindPlugin_missing()36     void testFindPlugin_missing()
37     {
38         const QString location = KPluginLoader::findPlugin(QStringLiteral("idonotexist"));
39         QVERIFY2(location.isEmpty(), qPrintable(location));
40     }
41 
testFindPlugin()42     void testFindPlugin()
43     {
44         const QString location = KPluginLoader::findPlugin(QStringLiteral("jsonplugin"));
45         QVERIFY2(!location.isEmpty(), qPrintable(location));
46     }
47 
48 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 84)
testPluginVersion()49     void testPluginVersion()
50     {
51         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
52         QCOMPARE(vplugin.pluginVersion(), quint32(5));
53 
54         KPluginLoader vplugin2(QStringLiteral("versionedplugin"));
55         QCOMPARE(vplugin2.pluginVersion(), quint32(5));
56 
57         KPluginLoader uplugin(QStringLiteral("unversionedplugin"));
58         QCOMPARE(uplugin.pluginVersion(), quint32(-1));
59 
60         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
61         QCOMPARE(jplugin.pluginVersion(), quint32(-1));
62 
63         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
64         QCOMPARE(eplugin.pluginVersion(), quint32(-1));
65 
66         KPluginLoader noplugin(QStringLiteral("idonotexist"));
67         QCOMPARE(noplugin.pluginVersion(), quint32(-1));
68     }
69 #endif
70 
testPluginName()71     void testPluginName()
72     {
73         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
74         QCOMPARE(vplugin.pluginName(), QString::fromLatin1("versionedplugin"));
75 
76         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
77         QCOMPARE(jplugin.pluginName(), QString::fromLatin1("jsonplugin"));
78 
79         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
80         QVERIFY2(eplugin.pluginName().isEmpty(), qPrintable(eplugin.pluginName()));
81 
82         KPluginLoader noplugin(QStringLiteral("idonotexist"));
83         QCOMPARE(noplugin.pluginName(), QString::fromLatin1("idonotexist"));
84     }
85 
testFactory()86     void testFactory()
87     {
88         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
89         QVERIFY(vplugin.factory());
90 
91         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
92         QVERIFY(jplugin.factory());
93 
94         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
95         QVERIFY(!eplugin.factory());
96 
97         KPluginLoader noplugin(QStringLiteral("idonotexist"));
98         QVERIFY(!noplugin.factory());
99     }
100 
testErrorString()101     void testErrorString()
102     {
103         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
104         QCOMPARE(eplugin.errorString(), QString::fromLatin1("there was an error"));
105     }
106 
testFileName()107     void testFileName()
108     {
109         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
110         QCOMPARE(QFileInfo(vplugin.fileName()).canonicalFilePath(), QFileInfo(QStringLiteral(VERSIONEDPLUGIN_FILE)).canonicalFilePath());
111 
112         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
113         QCOMPARE(QFileInfo(jplugin.fileName()).canonicalFilePath(), QFileInfo(QStringLiteral(JSONPLUGIN_FILE)).canonicalFilePath());
114 
115         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
116         QVERIFY2(eplugin.fileName().isEmpty(), qPrintable(eplugin.fileName()));
117 
118         KPluginLoader noplugin(QStringLiteral("idonotexist"));
119         QVERIFY2(noplugin.fileName().isEmpty(), qPrintable(noplugin.fileName()));
120     }
121 
testInstance()122     void testInstance()
123     {
124         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
125         QVERIFY(vplugin.instance());
126 
127         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
128         QVERIFY(jplugin.instance());
129 
130         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
131         QVERIFY(!eplugin.instance());
132 
133         KPluginLoader noplugin(QStringLiteral("idonotexist"));
134         QVERIFY(!noplugin.instance());
135     }
136 
testIsLoaded()137     void testIsLoaded()
138     {
139         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
140         QVERIFY(!vplugin.isLoaded());
141         QVERIFY(vplugin.load());
142         QVERIFY(vplugin.isLoaded());
143 
144         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
145         QVERIFY(!jplugin.isLoaded());
146         QVERIFY(jplugin.load());
147         QVERIFY(jplugin.isLoaded());
148 
149         KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
150         QVERIFY(!aplugin.isLoaded());
151         QVERIFY(aplugin.load());
152         QVERIFY(aplugin.isLoaded());
153         if (aplugin.unload()) {
154             QVERIFY(!aplugin.isLoaded());
155         } else {
156             qCDebug(KCOREADDONS_DEBUG) << "Could not unload alwaysunloadplugin:" << aplugin.errorString();
157         }
158 
159         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
160         QVERIFY(!eplugin.isLoaded());
161         QVERIFY(!eplugin.load());
162         QVERIFY(!eplugin.isLoaded());
163 
164         KPluginLoader noplugin(QStringLiteral("idonotexist"));
165         QVERIFY(!noplugin.isLoaded());
166         QVERIFY(!noplugin.load());
167         QVERIFY(!noplugin.isLoaded());
168     }
169 
testLoad()170     void testLoad()
171     {
172         KPluginLoader vplugin(QStringLiteral("versionedplugin"));
173         QVERIFY(vplugin.load());
174 
175         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
176         QVERIFY(jplugin.load());
177 
178         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
179         QVERIFY(!eplugin.load());
180 
181         KPluginLoader noplugin(QStringLiteral("idonotexist"));
182         QVERIFY(!noplugin.load());
183     }
184 
testLoadHints()185     void testLoadHints()
186     {
187         KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
188         aplugin.setLoadHints(QLibrary::ResolveAllSymbolsHint);
189         QCOMPARE(aplugin.loadHints(), QLibrary::ResolveAllSymbolsHint);
190     }
191 
testMetaData()192     void testMetaData()
193     {
194         KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
195         QJsonObject ametadata = aplugin.metaData();
196         QVERIFY(!ametadata.isEmpty());
197         QVERIFY(ametadata.keys().contains(QLatin1String("IID")));
198         QJsonValue ametadata_metadata = ametadata.value(QStringLiteral("MetaData"));
199         QVERIFY(ametadata_metadata.toObject().isEmpty());
200         QVERIFY(!aplugin.isLoaded()); // didn't load anything
201 
202         KPluginLoader jplugin(KPluginName(QStringLiteral("jsonplugin")));
203         QJsonObject jmetadata = jplugin.metaData();
204         QVERIFY(!jmetadata.isEmpty());
205         QJsonValue jmetadata_metadata = jmetadata.value(QStringLiteral("MetaData"));
206         QVERIFY(jmetadata_metadata.isObject());
207         QJsonObject jmetadata_obj = jmetadata_metadata.toObject();
208         QVERIFY(!jmetadata_obj.isEmpty());
209         QJsonValue comment = jmetadata_obj.value(QStringLiteral("KPlugin")).toObject().value(QStringLiteral("Description"));
210         QVERIFY(comment.isString());
211         QCOMPARE(comment.toString(), QString::fromLatin1("This is a plugin"));
212 
213         KPluginLoader eplugin(KPluginName::fromErrorString(QStringLiteral("there was an error")));
214         QVERIFY(eplugin.metaData().isEmpty());
215 
216         KPluginLoader noplugin(QStringLiteral("idonotexist"));
217         QVERIFY(noplugin.metaData().isEmpty());
218     }
219 
testUnload()220     void testUnload()
221     {
222         KPluginLoader aplugin(QStringLiteral("alwaysunloadplugin"));
223         QVERIFY(aplugin.load());
224         // may need QEXPECT_FAIL on some platforms...
225         QVERIFY(aplugin.unload());
226     }
227 
testInstantiatePlugins()228     void testInstantiatePlugins()
229     {
230         const QString plugin1Path = KPluginLoader::findPlugin(QStringLiteral("jsonplugin"));
231         QVERIFY2(!plugin1Path.isEmpty(), qPrintable(plugin1Path));
232         const QString plugin2Path = KPluginLoader::findPlugin(QStringLiteral("unversionedplugin"));
233         QVERIFY2(!plugin2Path.isEmpty(), qPrintable(plugin2Path));
234         const QString plugin3Path = KPluginLoader::findPlugin(QStringLiteral("jsonplugin2"));
235         QVERIFY2(!plugin3Path.isEmpty(), qPrintable(plugin3Path));
236 
237         QTemporaryDir temp;
238         QVERIFY(temp.isValid());
239         QDir dir(temp.path());
240         QVERIFY2(QFile::copy(plugin1Path, dir.absoluteFilePath(QFileInfo(plugin1Path).fileName())),
241                  qPrintable(dir.absoluteFilePath(QFileInfo(plugin1Path).fileName())));
242         QVERIFY2(QFile::copy(plugin2Path, dir.absoluteFilePath(QFileInfo(plugin2Path).fileName())),
243                  qPrintable(dir.absoluteFilePath(QFileInfo(plugin2Path).fileName())));
244         QVERIFY2(QFile::copy(plugin3Path, dir.absoluteFilePath(QFileInfo(plugin3Path).fileName())),
245                  qPrintable(dir.absoluteFilePath(QFileInfo(plugin3Path).fileName())));
246 
247         // only jsonplugin, since unversionedplugin has no json metadata
248         QList<QObject *> plugins = KPluginLoader::instantiatePlugins(temp.path());
249         QCOMPARE(plugins.size(), 2);
250         QStringList classNames = QStringList() << QString::fromLatin1(plugins[0]->metaObject()->className())
251                                                << QString::fromLatin1(plugins[1]->metaObject()->className());
252         classNames.sort();
253         QCOMPARE(classNames[0], QStringLiteral("jsonplugin2"));
254         QCOMPARE(classNames[1], QStringLiteral("jsonpluginfa"));
255         qDeleteAll(plugins);
256 
257         // try filter
258         plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData &md) {
259             return md.pluginId() == QLatin1String("jsonplugin");
260         });
261         QCOMPARE(plugins.size(), 1);
262         QCOMPARE(plugins[0]->metaObject()->className(), "jsonpluginfa");
263         qDeleteAll(plugins);
264 
265         plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData &md) {
266             return md.pluginId() == QLatin1String("unversionedplugin");
267         });
268         QCOMPARE(plugins.size(), 0);
269 
270         plugins = KPluginLoader::instantiatePlugins(temp.path(), [](const KPluginMetaData &md) {
271             return md.pluginId() == QLatin1String("foobar"); // ID does not match file name, is set in JSON
272         });
273         QCOMPARE(plugins.size(), 1);
274         QCOMPARE(plugins[0]->metaObject()->className(), "jsonplugin2");
275         qDeleteAll(plugins);
276 
277         // check that parent gets set
278         plugins = KPluginLoader::instantiatePlugins(
279             temp.path(),
280             [](const KPluginMetaData &) {
281                 return true;
282             },
283             this);
284         QCOMPARE(plugins.size(), 2);
285         QCOMPARE(plugins[0]->parent(), this);
286         QCOMPARE(plugins[1]->parent(), this);
287         qDeleteAll(plugins);
288 
289         const QString subDirName = dir.dirName();
290         QVERIFY(dir.cdUp()); // should now point to /tmp on Linux
291         LibraryPathRestorer restorer(QCoreApplication::libraryPaths());
292         // instantiate using relative path
293         // make sure library path is set up correctly
294         QCoreApplication::setLibraryPaths(QStringList() << dir.absolutePath());
295         QVERIFY(!QDir::isAbsolutePath(subDirName));
296         plugins = KPluginLoader::instantiatePlugins(subDirName);
297         QCOMPARE(plugins.size(), 2);
298         classNames = QStringList() << QString::fromLatin1(plugins[0]->metaObject()->className()) << QString::fromLatin1(plugins[1]->metaObject()->className());
299         classNames.sort();
300         QCOMPARE(classNames[0], QStringLiteral("jsonplugin2"));
301         QCOMPARE(classNames[1], QStringLiteral("jsonpluginfa"));
302         qDeleteAll(plugins);
303     }
304 
testForEachPlugin()305     void testForEachPlugin()
306     {
307         const QString jsonPluginSrc = KPluginLoader::findPlugin(QStringLiteral("jsonplugin"));
308         QVERIFY2(!jsonPluginSrc.isEmpty(), qPrintable(jsonPluginSrc));
309         const QString unversionedPluginSrc = KPluginLoader::findPlugin(QStringLiteral("unversionedplugin"));
310         QVERIFY2(!unversionedPluginSrc.isEmpty(), qPrintable(unversionedPluginSrc));
311         const QString jsonPlugin2Src = KPluginLoader::findPlugin(QStringLiteral("jsonplugin2"));
312         QVERIFY2(!jsonPlugin2Src.isEmpty(), qPrintable(jsonPlugin2Src));
313 
314         QTemporaryDir temp;
315         QVERIFY(temp.isValid());
316         QDir dir(temp.path());
317         QVERIFY(dir.mkdir(QStringLiteral("for-each-plugin")));
318         QVERIFY(dir.cd(QStringLiteral("for-each-plugin")));
319         const QString jsonPluginDest = dir.absoluteFilePath(QFileInfo(jsonPluginSrc).fileName());
320         QVERIFY2(QFile::copy(jsonPluginSrc, jsonPluginDest), qPrintable(jsonPluginDest));
321         const QString unversionedPluginDest = dir.absoluteFilePath(QFileInfo(unversionedPluginSrc).fileName());
322         QVERIFY2(QFile::copy(unversionedPluginSrc, unversionedPluginDest), qPrintable(unversionedPluginDest));
323         // copy jsonplugin2 to a "for-each-plugin" subdirectory in a different directory
324         QTemporaryDir temp2;
325         QVERIFY(temp2.isValid());
326         QDir dir2(temp2.path());
327         QVERIFY(dir2.mkdir(QStringLiteral("for-each-plugin")));
328         QVERIFY(dir2.cd(QStringLiteral("for-each-plugin")));
329         const QString jsonPlugin2Dest = dir2.absoluteFilePath(QFileInfo(jsonPlugin2Src).fileName());
330         QVERIFY2(QFile::copy(jsonPlugin2Src, jsonPlugin2Dest), qPrintable(jsonPlugin2Dest));
331 
332         QStringList foundPlugins;
333         QStringList expectedPlugins;
334         const auto addToFoundPlugins = [&](const QString &path) {
335             QVERIFY(!path.isEmpty());
336             foundPlugins.append(path);
337         };
338 
339         // test finding with absolute path
340         expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest;
341         expectedPlugins.sort();
342         KPluginLoader::forEachPlugin(dir.path(), addToFoundPlugins);
343         foundPlugins.sort();
344         QCOMPARE(foundPlugins, expectedPlugins);
345 
346         expectedPlugins = QStringList() << jsonPlugin2Dest;
347         expectedPlugins.sort();
348         foundPlugins.clear();
349         KPluginLoader::forEachPlugin(dir2.path(), addToFoundPlugins);
350         foundPlugins.sort();
351         QCOMPARE(foundPlugins, expectedPlugins);
352 
353         // now test relative paths
354 
355         LibraryPathRestorer restorer(QCoreApplication::libraryPaths());
356         QCoreApplication::setLibraryPaths(QStringList() << temp.path());
357         expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest;
358         expectedPlugins.sort();
359         foundPlugins.clear();
360         KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins);
361         foundPlugins.sort();
362         QCOMPARE(foundPlugins, expectedPlugins);
363 
364         QCoreApplication::setLibraryPaths(QStringList() << temp2.path());
365         expectedPlugins = QStringList() << jsonPlugin2Dest;
366         expectedPlugins.sort();
367         foundPlugins.clear();
368         KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins);
369         foundPlugins.sort();
370         QCOMPARE(foundPlugins, expectedPlugins);
371 
372         QCoreApplication::setLibraryPaths(QStringList() << temp.path() << temp2.path());
373         expectedPlugins = QStringList() << jsonPluginDest << unversionedPluginDest << jsonPlugin2Dest;
374         expectedPlugins.sort();
375         foundPlugins.clear();
376         KPluginLoader::forEachPlugin(QStringLiteral("for-each-plugin"), addToFoundPlugins);
377         foundPlugins.sort();
378         QCOMPARE(foundPlugins, expectedPlugins);
379     }
380 #endif
381 };
382 
383 QTEST_MAIN(KPluginLoaderTest)
384 
385 #include "kpluginloadertest.moc"
386