1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Copyright (C) 2018 Intel Corporation. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the test suite of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | |
30 | #include <QtTest/QtTest> |
31 | #include <qdir.h> |
32 | #include <qpluginloader.h> |
33 | #include "theplugin/plugininterface.h" |
34 | |
35 | #if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O) |
36 | # include <QtCore/private/qmachparser_p.h> |
37 | #endif |
38 | |
39 | // Helper macros to let us know if some suffixes are valid |
40 | #define bundle_VALID false |
41 | #define dylib_VALID false |
42 | #define sl_VALID false |
43 | #define a_VALID false |
44 | #define so_VALID false |
45 | #define dll_VALID false |
46 | |
47 | #if defined(Q_OS_DARWIN) |
48 | # undef bundle_VALID |
49 | # undef dylib_VALID |
50 | # undef so_VALID |
51 | # define bundle_VALID true |
52 | # define dylib_VALID true |
53 | # define so_VALID true |
54 | //# ifdef QT_NO_DEBUG |
55 | # define SUFFIX ".dylib" |
56 | //# else |
57 | //# define SUFFIX "_debug.dylib" |
58 | //#endif |
59 | # define PREFIX "lib" |
60 | |
61 | #elif defined(Q_OS_HPUX) && !defined(__ia64) |
62 | # undef sl_VALID |
63 | # define sl_VALID true |
64 | # define SUFFIX ".sl" |
65 | # define PREFIX "lib" |
66 | |
67 | #elif defined(Q_OS_AIX) |
68 | # undef a_VALID |
69 | # undef so_VALID |
70 | # define a_VALID true |
71 | # define so_VALID true |
72 | # define SUFFIX ".so" |
73 | # define PREFIX "lib" |
74 | |
75 | #elif defined(Q_OS_WIN) |
76 | # undef dll_VALID |
77 | # define dll_VALID true |
78 | //# ifdef QT_NO_DEBUG |
79 | # define SUFFIX ".dll" |
80 | //# else |
81 | //# define SUFFIX "d.dll" |
82 | //# endif |
83 | # define PREFIX "" |
84 | |
85 | #else // all other Unix |
86 | # undef so_VALID |
87 | # define so_VALID true |
88 | # define SUFFIX ".so" |
89 | # define PREFIX "lib" |
90 | #endif |
91 | |
92 | static QString sys_qualifiedLibraryName(const QString &fileName) |
93 | { |
94 | QString name = QLatin1String("bin/" ) + QLatin1String(PREFIX) + fileName + QLatin1String(SUFFIX); |
95 | const QString libname = QFINDTESTDATA(name); |
96 | QFileInfo fi(libname); |
97 | if (fi.exists()) |
98 | return fi.canonicalFilePath(); |
99 | return libname; |
100 | } |
101 | |
102 | QT_FORWARD_DECLARE_CLASS(QPluginLoader) |
103 | class tst_QPluginLoader : public QObject |
104 | { |
105 | Q_OBJECT |
106 | public slots: |
107 | void cleanup(); |
108 | private slots: |
109 | void errorString(); |
110 | void loadHints(); |
111 | void deleteinstanceOnUnload(); |
112 | void loadDebugObj(); |
113 | void loadCorruptElf(); |
114 | void loadMachO_data(); |
115 | void loadMachO(); |
116 | #if defined (Q_OS_UNIX) |
117 | void loadGarbage(); |
118 | #endif |
119 | void relativePath(); |
120 | void absolutePath(); |
121 | void reloadPlugin(); |
122 | void preloadedPlugin_data(); |
123 | void preloadedPlugin(); |
124 | void staticPlugins(); |
125 | }; |
126 | |
127 | Q_IMPORT_PLUGIN(StaticPlugin) |
128 | |
129 | void tst_QPluginLoader::cleanup() |
130 | { |
131 | // check if the library/plugin was leaked |
132 | // we can't use QPluginLoader::isLoaded here because on some platforms the plugin is always loaded by QPluginLoader. |
133 | // Also, if this test fails once, it will keep on failing because we can't force the unload, |
134 | // so we report it only once. |
135 | static bool failedAlready = false; |
136 | if (!failedAlready) { |
137 | QLibrary lib(sys_qualifiedLibraryName(fileName: "theplugin" )); |
138 | failedAlready = true; |
139 | QVERIFY2(!lib.isLoaded(), "Plugin was leaked - will not check again" ); |
140 | failedAlready = false; |
141 | } |
142 | } |
143 | |
144 | void tst_QPluginLoader::errorString() |
145 | { |
146 | #if !defined(QT_SHARED) |
147 | QSKIP("This test requires Qt to create shared libraries." ); |
148 | #endif |
149 | |
150 | const QString unknown(QLatin1String("Unknown error" )); |
151 | |
152 | { |
153 | QPluginLoader loader; // default constructed |
154 | bool loaded = loader.load(); |
155 | QCOMPARE(loader.errorString(), unknown); |
156 | QVERIFY(!loaded); |
157 | |
158 | QObject *obj = loader.instance(); |
159 | QCOMPARE(loader.errorString(), unknown); |
160 | QCOMPARE(obj, static_cast<QObject*>(0)); |
161 | |
162 | bool unloaded = loader.unload(); |
163 | QCOMPARE(loader.errorString(), unknown); |
164 | QVERIFY(!unloaded); |
165 | } |
166 | { |
167 | QPluginLoader loader( sys_qualifiedLibraryName(fileName: "tst_qpluginloaderlib" )); //not a plugin |
168 | bool loaded = loader.load(); |
169 | QVERIFY(loader.errorString() != unknown); |
170 | QVERIFY(!loaded); |
171 | |
172 | QObject *obj = loader.instance(); |
173 | QVERIFY(loader.errorString() != unknown); |
174 | QCOMPARE(obj, static_cast<QObject*>(0)); |
175 | |
176 | bool unloaded = loader.unload(); |
177 | QVERIFY(loader.errorString() != unknown); |
178 | QVERIFY(!unloaded); |
179 | } |
180 | |
181 | { |
182 | QPluginLoader loader( sys_qualifiedLibraryName(fileName: "nosuchfile" )); //not a file |
183 | bool loaded = loader.load(); |
184 | QVERIFY(loader.errorString() != unknown); |
185 | QVERIFY(!loaded); |
186 | |
187 | QObject *obj = loader.instance(); |
188 | QVERIFY(loader.errorString() != unknown); |
189 | QCOMPARE(obj, static_cast<QObject*>(0)); |
190 | |
191 | bool unloaded = loader.unload(); |
192 | QVERIFY(loader.errorString() != unknown); |
193 | QVERIFY(!unloaded); |
194 | } |
195 | |
196 | #if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) && !defined(Q_OS_HPUX) |
197 | { |
198 | QPluginLoader loader( sys_qualifiedLibraryName(fileName: "almostplugin" )); //a plugin with unresolved symbols |
199 | loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); |
200 | bool loaded = loader.load(); |
201 | QVERIFY(loader.errorString() != unknown); |
202 | QVERIFY(!loaded); |
203 | |
204 | QObject *obj = loader.instance(); |
205 | QVERIFY(loader.errorString() != unknown); |
206 | QCOMPARE(obj, static_cast<QObject*>(0)); |
207 | |
208 | bool unloaded = loader.unload(); |
209 | QVERIFY(loader.errorString() != unknown); |
210 | QVERIFY(!unloaded); |
211 | } |
212 | #endif |
213 | |
214 | { |
215 | QPluginLoader loader( sys_qualifiedLibraryName(fileName: "theplugin" )); //a plugin |
216 | |
217 | // Check metadata |
218 | const QJsonObject metaData = loader.metaData(); |
219 | QCOMPARE(metaData.value("IID" ).toString(), QStringLiteral("org.qt-project.Qt.autotests.plugininterface" )); |
220 | const QJsonObject kpluginObject = metaData.value(key: "MetaData" ).toObject().value(key: "KPlugin" ).toObject(); |
221 | QCOMPARE(kpluginObject.value("Name[mr]" ).toString(), QString::fromUtf8("चौकट भूमिती" )); |
222 | |
223 | // Load |
224 | QCOMPARE(loader.load(), true); |
225 | QCOMPARE(loader.errorString(), unknown); |
226 | |
227 | QVERIFY(loader.instance() != static_cast<QObject*>(0)); |
228 | QCOMPARE(loader.errorString(), unknown); |
229 | |
230 | // Make sure that plugin really works |
231 | PluginInterface* theplugin = qobject_cast<PluginInterface*>(object: loader.instance()); |
232 | QString pluginName = theplugin->pluginName(); |
233 | QCOMPARE(pluginName, QLatin1String("Plugin ok" )); |
234 | |
235 | QCOMPARE(loader.unload(), true); |
236 | QCOMPARE(loader.errorString(), unknown); |
237 | } |
238 | } |
239 | |
240 | void tst_QPluginLoader::loadHints() |
241 | { |
242 | #if !defined(QT_SHARED) |
243 | QSKIP("This test requires Qt to create shared libraries." ); |
244 | #endif |
245 | QPluginLoader loader; |
246 | QCOMPARE(loader.loadHints(), QLibrary::LoadHints{}); //Do not crash |
247 | loader.setLoadHints(QLibrary::ResolveAllSymbolsHint); |
248 | loader.setFileName( sys_qualifiedLibraryName(fileName: "theplugin" )); //a plugin |
249 | QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint); |
250 | } |
251 | |
252 | void tst_QPluginLoader::deleteinstanceOnUnload() |
253 | { |
254 | #if !defined(QT_SHARED) |
255 | QSKIP("This test requires Qt to create shared libraries." ); |
256 | #endif |
257 | for (int pass = 0; pass < 2; ++pass) { |
258 | QPluginLoader loader1; |
259 | loader1.setFileName( sys_qualifiedLibraryName(fileName: "theplugin" )); //a plugin |
260 | if (pass == 0) |
261 | loader1.load(); // not recommended, instance() should do the job. |
262 | PluginInterface *instance1 = qobject_cast<PluginInterface*>(object: loader1.instance()); |
263 | QVERIFY(instance1); |
264 | QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok" )); |
265 | |
266 | QPluginLoader loader2; |
267 | loader2.setFileName( sys_qualifiedLibraryName(fileName: "theplugin" )); //a plugin |
268 | if (pass == 0) |
269 | loader2.load(); // not recommended, instance() should do the job. |
270 | PluginInterface *instance2 = qobject_cast<PluginInterface*>(object: loader2.instance()); |
271 | QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok" )); |
272 | |
273 | QSignalSpy spy1(loader1.instance(), &QObject::destroyed); |
274 | QSignalSpy spy2(loader2.instance(), &QObject::destroyed); |
275 | QVERIFY(spy1.isValid()); |
276 | QVERIFY(spy2.isValid()); |
277 | if (pass == 0) { |
278 | QCOMPARE(loader2.unload(), false); // refcount not reached 0, not really unloaded |
279 | QCOMPARE(spy1.count(), 0); |
280 | QCOMPARE(spy2.count(), 0); |
281 | } |
282 | QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok" )); |
283 | QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok" )); |
284 | QVERIFY(loader1.unload()); // refcount reached 0, did really unload |
285 | QCOMPARE(spy1.count(), 1); |
286 | QCOMPARE(spy2.count(), 1); |
287 | } |
288 | } |
289 | |
290 | void tst_QPluginLoader::loadDebugObj() |
291 | { |
292 | #if !defined(QT_SHARED) |
293 | QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds" ); |
294 | #endif |
295 | #if defined (__ELF__) |
296 | QVERIFY(QFile::exists(QFINDTESTDATA("elftest/debugobj.so" ))); |
297 | QPluginLoader lib1(QFINDTESTDATA("elftest/debugobj.so" )); |
298 | QCOMPARE(lib1.load(), false); |
299 | #endif |
300 | } |
301 | |
302 | void tst_QPluginLoader::loadCorruptElf() |
303 | { |
304 | #if !defined(QT_SHARED) |
305 | QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds" ); |
306 | #endif |
307 | #if defined (__ELF__) |
308 | if (sizeof(void*) == 8) { |
309 | QVERIFY(QFile::exists(QFINDTESTDATA("elftest/corrupt1.elf64.so" ))); |
310 | |
311 | QPluginLoader lib1(QFINDTESTDATA("elftest/corrupt1.elf64.so" )); |
312 | QCOMPARE(lib1.load(), false); |
313 | QVERIFY2(lib1.errorString().contains("not an ELF object" ), qPrintable(lib1.errorString())); |
314 | |
315 | QPluginLoader lib2(QFINDTESTDATA("elftest/corrupt2.elf64.so" )); |
316 | QCOMPARE(lib2.load(), false); |
317 | QVERIFY2(lib2.errorString().contains("invalid" ), qPrintable(lib2.errorString())); |
318 | |
319 | QPluginLoader lib3(QFINDTESTDATA("elftest/corrupt3.elf64.so" )); |
320 | QCOMPARE(lib3.load(), false); |
321 | QVERIFY2(lib3.errorString().contains("invalid" ), qPrintable(lib3.errorString())); |
322 | } else if (sizeof(void*) == 4) { |
323 | QPluginLoader libW(QFINDTESTDATA("elftest/corrupt3.elf64.so" )); |
324 | QCOMPARE(libW.load(), false); |
325 | QVERIFY2(libW.errorString().contains("architecture" ), qPrintable(libW.errorString())); |
326 | } else { |
327 | QFAIL("Please port QElfParser to this platform or blacklist this test." ); |
328 | } |
329 | #endif |
330 | } |
331 | |
332 | void tst_QPluginLoader::loadMachO_data() |
333 | { |
334 | #if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O) |
335 | QTest::addColumn<int>("parseResult" ); |
336 | |
337 | QTest::newRow("/dev/null" ) << int(QMachOParser::NotSuitable); |
338 | QTest::newRow("elftest/debugobj.so" ) << int(QMachOParser::NotSuitable); |
339 | QTest::newRow("tst_qpluginloader.cpp" ) << int(QMachOParser::NotSuitable); |
340 | QTest::newRow("tst_qpluginloader" ) << int(QMachOParser::NotSuitable); |
341 | |
342 | # ifdef Q_PROCESSOR_X86_64 |
343 | QTest::newRow("machtest/good.x86_64.dylib" ) << int(QMachOParser::QtMetaDataSection); |
344 | QTest::newRow("machtest/good.arm64.dylib" ) << int(QMachOParser::NotSuitable); |
345 | QTest::newRow("machtest/good.fat.no-x86_64.dylib" ) << int(QMachOParser::NotSuitable); |
346 | QTest::newRow("machtest/good.fat.no-arm64.dylib" ) << int(QMachOParser::QtMetaDataSection); |
347 | # elif defined(Q_PROCESSOR_ARM) |
348 | QTest::newRow("machtest/good.arm64.dylib" ) << int(QMachOParser::QtMetaDataSection); |
349 | QTest::newRow("machtest/good.x86_64.dylib" ) << int(QMachOParser::NotSuitable); |
350 | QTest::newRow("machtest/good.fat.no-arm64.dylib" ) << int(QMachOParser::NotSuitable); |
351 | QTest::newRow("machtest/good.fat.no-x86_64.dylib" ) << int(QMachOParser::QtMetaDataSection); |
352 | # endif |
353 | |
354 | QTest::newRow("machtest/good.fat.all.dylib" ) << int(QMachOParser::QtMetaDataSection); |
355 | QTest::newRow("machtest/good.fat.stub-x86_64.dylib" ) << int(QMachOParser::NotSuitable); |
356 | QTest::newRow("machtest/good.fat.stub-arm64.dylib" ) << int(QMachOParser::NotSuitable); |
357 | |
358 | QDir d(QFINDTESTDATA("machtest" )); |
359 | QStringList badlist = d.entryList(QStringList() << "bad*.dylib" ); |
360 | foreach (const QString &bad, badlist) |
361 | QTest::newRow(qPrintable("machtest/" + bad)) << int(QMachOParser::NotSuitable); |
362 | #endif |
363 | } |
364 | |
365 | void tst_QPluginLoader::loadMachO() |
366 | { |
367 | #if defined(QT_BUILD_INTERNAL) && defined(Q_OF_MACH_O) |
368 | QFile f(QFINDTESTDATA(QTest::currentDataTag())); |
369 | QVERIFY(f.open(QIODevice::ReadOnly)); |
370 | QByteArray data = f.readAll(); |
371 | |
372 | qsizetype pos; |
373 | qsizetype len; |
374 | QString errorString; |
375 | int r = QMachOParser::parse(data.constData(), data.size(), f.fileName(), &errorString, &pos, &len); |
376 | |
377 | QFETCH(int, parseResult); |
378 | QCOMPARE(r, parseResult); |
379 | |
380 | if (r == QMachOParser::NotSuitable) |
381 | return; |
382 | |
383 | QVERIFY(pos > 0); |
384 | QVERIFY(len >= sizeof(void*)); |
385 | QVERIFY(pos + long(len) < data.size()); |
386 | QCOMPARE(pos & (sizeof(void*) - 1), 0UL); |
387 | |
388 | void *value = *(void**)(data.constData() + pos); |
389 | QCOMPARE(value, sizeof(void*) > 4 ? (void*)(0xc0ffeec0ffeeL) : (void*)0xc0ffee); |
390 | |
391 | // now that we know it's valid, let's try to make it invalid |
392 | ulong offeredlen = pos; |
393 | do { |
394 | --offeredlen; |
395 | r = QMachOParser::parse(data.constData(), offeredlen, f.fileName(), &errorString, &pos, &len); |
396 | QVERIFY2(r == QMachOParser::NotSuitable, qPrintable(QString("Failed at size 0x%1" ).arg(offeredlen, 0, 16))); |
397 | } while (offeredlen); |
398 | #endif |
399 | } |
400 | |
401 | #if defined (Q_OS_UNIX) |
402 | void tst_QPluginLoader::loadGarbage() |
403 | { |
404 | #if !defined(QT_SHARED) |
405 | QSKIP("This test requires a shared build of Qt, as QPluginLoader::setFileName is a no-op in static builds" ); |
406 | #endif |
407 | for (int i=0; i<5; i++) { |
408 | const QString name = QLatin1String("elftest/garbage" ) + QString::number(i + 1) + QLatin1String(".so" ); |
409 | QPluginLoader lib(QFINDTESTDATA(name)); |
410 | QCOMPARE(lib.load(), false); |
411 | QVERIFY(lib.errorString() != QString("Unknown error" )); |
412 | } |
413 | } |
414 | #endif |
415 | |
416 | void tst_QPluginLoader::relativePath() |
417 | { |
418 | #if !defined(QT_SHARED) |
419 | QSKIP("This test requires Qt to create shared libraries." ); |
420 | #endif |
421 | // Windows binaries run from release and debug subdirs, so we can't rely on the current dir. |
422 | const QString binDir = QFINDTESTDATA("bin" ); |
423 | QVERIFY(!binDir.isEmpty()); |
424 | QCoreApplication::addLibraryPath(binDir); |
425 | QPluginLoader loader("theplugin" ); |
426 | loader.load(); // not recommended, instance() should do the job. |
427 | PluginInterface *instance = qobject_cast<PluginInterface*>(object: loader.instance()); |
428 | QVERIFY(instance); |
429 | QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok" )); |
430 | QVERIFY(loader.unload()); |
431 | } |
432 | |
433 | void tst_QPluginLoader::absolutePath() |
434 | { |
435 | #if !defined(QT_SHARED) |
436 | QSKIP("This test requires Qt to create shared libraries." ); |
437 | #endif |
438 | // Windows binaries run from release and debug subdirs, so we can't rely on the current dir. |
439 | const QString binDir = QFINDTESTDATA("bin" ); |
440 | QVERIFY(!binDir.isEmpty()); |
441 | QVERIFY(QDir::isAbsolutePath(binDir)); |
442 | QPluginLoader loader(binDir + "/theplugin" ); |
443 | loader.load(); // not recommended, instance() should do the job. |
444 | PluginInterface *instance = qobject_cast<PluginInterface*>(object: loader.instance()); |
445 | QVERIFY(instance); |
446 | QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok" )); |
447 | QVERIFY(loader.unload()); |
448 | } |
449 | |
450 | void tst_QPluginLoader::reloadPlugin() |
451 | { |
452 | #if !defined(QT_SHARED) |
453 | QSKIP("This test requires Qt to create shared libraries." ); |
454 | #endif |
455 | QPluginLoader loader; |
456 | loader.setFileName( sys_qualifiedLibraryName(fileName: "theplugin" )); //a plugin |
457 | loader.load(); // not recommended, instance() should do the job. |
458 | PluginInterface *instance = qobject_cast<PluginInterface*>(object: loader.instance()); |
459 | QVERIFY(instance); |
460 | QCOMPARE(instance->pluginName(), QLatin1String("Plugin ok" )); |
461 | |
462 | QSignalSpy spy(loader.instance(), &QObject::destroyed); |
463 | QVERIFY(spy.isValid()); |
464 | QVERIFY(loader.unload()); // refcount reached 0, did really unload |
465 | QCOMPARE(spy.count(), 1); |
466 | |
467 | // reload plugin |
468 | QVERIFY(loader.load()); |
469 | QVERIFY(loader.isLoaded()); |
470 | |
471 | PluginInterface *instance2 = qobject_cast<PluginInterface*>(object: loader.instance()); |
472 | QVERIFY(instance2); |
473 | QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok" )); |
474 | |
475 | QVERIFY(loader.unload()); |
476 | } |
477 | |
478 | void tst_QPluginLoader::preloadedPlugin_data() |
479 | { |
480 | QTest::addColumn<bool>(name: "doLoad" ); |
481 | QTest::addColumn<QString>(name: "libname" ); |
482 | QTest::newRow(dataTag: "create-plugin" ) << false << sys_qualifiedLibraryName(fileName: "theplugin" ); |
483 | QTest::newRow(dataTag: "load-plugin" ) << true << sys_qualifiedLibraryName(fileName: "theplugin" ); |
484 | QTest::newRow(dataTag: "create-non-plugin" ) << false << sys_qualifiedLibraryName(fileName: "tst_qpluginloaderlib" ); |
485 | QTest::newRow(dataTag: "load-non-plugin" ) << true << sys_qualifiedLibraryName(fileName: "tst_qpluginloaderlib" ); |
486 | } |
487 | |
488 | void tst_QPluginLoader::preloadedPlugin() |
489 | { |
490 | #if !defined(QT_SHARED) |
491 | QSKIP("This test requires Qt to create shared libraries." ); |
492 | #endif |
493 | // check that using QPluginLoader does not interfere with QLibrary |
494 | QFETCH(QString, libname); |
495 | QLibrary lib(libname); |
496 | QVERIFY(lib.load()); |
497 | |
498 | typedef int *(*pf_t)(); |
499 | pf_t pf = (pf_t)lib.resolve(symbol: "pointerAddress" ); |
500 | QVERIFY(pf); |
501 | |
502 | int *pluginVariable = pf(); |
503 | QVERIFY(pluginVariable); |
504 | QCOMPARE(*pluginVariable, 0xc0ffee); |
505 | |
506 | { |
507 | // load the plugin |
508 | QPluginLoader loader(libname); |
509 | QFETCH(bool, doLoad); |
510 | if (doLoad && loader.load()) { |
511 | // unload() returns false because QLibrary has it loaded |
512 | QVERIFY(!loader.unload()); |
513 | } |
514 | } |
515 | |
516 | QVERIFY(lib.isLoaded()); |
517 | |
518 | // if the library was unloaded behind our backs, the following will crash: |
519 | QCOMPARE(*pluginVariable, 0xc0ffee); |
520 | QVERIFY(lib.unload()); |
521 | } |
522 | |
523 | void tst_QPluginLoader::staticPlugins() |
524 | { |
525 | const QObjectList instances = QPluginLoader::staticInstances(); |
526 | QVERIFY(instances.size()); |
527 | |
528 | bool found = false; |
529 | for (QObject *obj : instances) { |
530 | found = obj->metaObject()->className() == QLatin1String("StaticPlugin" ); |
531 | if (found) |
532 | break; |
533 | } |
534 | QVERIFY(found); |
535 | |
536 | const auto plugins = QPluginLoader::staticPlugins(); |
537 | QCOMPARE(plugins.size(), instances.size()); |
538 | |
539 | // find the metadata |
540 | QJsonObject metaData; |
541 | for (const auto &p : plugins) { |
542 | metaData = p.metaData(); |
543 | found = metaData.value(key: "className" ).toString() == QLatin1String("StaticPlugin" ); |
544 | if (found) |
545 | break; |
546 | } |
547 | QVERIFY(found); |
548 | |
549 | // We don't store the patch release version anymore (since 5.13) |
550 | QCOMPARE(metaData.value("version" ).toInt() / 0x100, QT_VERSION / 0x100); |
551 | QCOMPARE(metaData.value("IID" ).toString(), "SomeIID" ); |
552 | QCOMPARE(metaData.value("ExtraMetaData" ), QJsonArray({ "StaticPlugin" , "foo" })); |
553 | QCOMPARE(metaData.value("URI" ).toString(), "qt.test.pluginloader.staticplugin" ); |
554 | } |
555 | |
556 | |
557 | QTEST_MAIN(tst_QPluginLoader) |
558 | #include "tst_qpluginloader.moc" |
559 | |