| 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 |  |