1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42
43 #include <QtTest/QtTest>
44 #include <qdir.h>
45 #include <qpluginloader.h>
46 #include "theplugin/plugininterface.h"
47
48 // Helper macros to let us know if some suffixes are valid
49 #define bundle_VALID false
50 #define dylib_VALID false
51 #define sl_VALID false
52 #define a_VALID false
53 #define so_VALID false
54 #define dll_VALID false
55
56 #if defined(Q_OS_DARWIN)
57 # undef bundle_VALID
58 # undef dylib_VALID
59 # undef so_VALID
60 # define bundle_VALID true
61 # define dylib_VALID true
62 # define so_VALID true
63 # define SUFFIX ".dylib"
64 # define PREFIX "lib"
65
66 #elif defined(Q_OS_HPUX) && !defined(__ia64)
67 # undef sl_VALID
68 # define sl_VALID true
69 # define SUFFIX ".sl"
70 # define PREFIX "lib"
71
72 #elif defined(Q_OS_AIX)
73 # undef a_VALID
74 # undef so_VALID
75 # define a_VALID true
76 # define so_VALID true
77 # define SUFFIX ".so"
78 # define PREFIX "lib"
79
80 #elif defined(Q_OS_WIN)
81 # undef dll_VALID
82 # define dll_VALID true
83 # define SUFFIX ".dll"
84 # define PREFIX ""
85
86 #elif defined(Q_OS_SYMBIAN)
87 # undef dll_VALID
88 # define dll_VALID true
89 # define SUFFIX ".dll"
90 # define PREFIX ""
91
92 #else // all other Unix
93 # undef so_VALID
94 # define so_VALID true
95 # define SUFFIX ".so"
96 # define PREFIX "lib"
97 #endif
98
99 //TESTED_CLASS=
100 //TESTED_FILES=
101
102 QT_FORWARD_DECLARE_CLASS(QPluginLoader)
103 class tst_QPluginLoader : public QObject
104 {
105 Q_OBJECT
106
107 private slots:
108 void initTestCase();
109 void errorString();
110 void loadHints();
111 void deleteinstanceOnUnload();
112 void checkingStubsFromDifferentDrives();
113 void loadDebugObj();
114 void loadCorruptElf_data();
115 void loadCorruptElf();
116 void loadGarbage();
117
118 private:
119 QString qualifiedLibraryName(const QString &fileName) const;
120
121 QString m_bin;
122 };
123
initTestCase()124 void tst_QPluginLoader::initTestCase()
125 {
126 QDir workingDirectory = QDir::current();
127 #ifdef Q_OS_WIN
128 // cd up to be able to locate the binaries of the sub-processes.
129 if (workingDirectory.absolutePath().endsWith(QLatin1String("/debug"), Qt::CaseInsensitive)
130 || workingDirectory.absolutePath().endsWith(QLatin1String("/release"), Qt::CaseInsensitive)) {
131 QVERIFY(workingDirectory.cdUp());
132 QVERIFY(QDir::setCurrent(workingDirectory.absolutePath()));
133 }
134 #endif
135 m_bin = workingDirectory.absoluteFilePath(QLatin1String("bin"));
136 QVERIFY(QFileInfo(m_bin).isDir());
137 }
138
qualifiedLibraryName(const QString & fileName) const139 QString tst_QPluginLoader::qualifiedLibraryName(const QString &fileName) const
140 {
141 return m_bin + QLatin1Char('/') + QLatin1String(PREFIX)
142 + fileName + QLatin1String(SUFFIX);
143 }
144
msgCannotLoadPlugin(const QString & plugin,const QString & why)145 static inline QByteArray msgCannotLoadPlugin(const QString &plugin, const QString &why)
146 {
147 return QString::fromLatin1("Cannot load plugin '%1' from '%2': %3")
148 .arg(QDir::toNativeSeparators(plugin),
149 QDir::toNativeSeparators(QDir::currentPath()),
150 why).toLocal8Bit();
151 }
152
153 //#define SHOW_ERRORS 1
154
errorString()155 void tst_QPluginLoader::errorString()
156 {
157 #if defined(Q_OS_WINCE)
158 // On WinCE we need an QCoreApplication object for current dir
159 int argc = 0;
160 QCoreApplication app(argc,0);
161 #endif
162 const QString unknown(QLatin1String("Unknown error"));
163
164 {
165 QPluginLoader loader; // default constructed
166 bool loaded = loader.load();
167 #ifdef SHOW_ERRORS
168 qDebug() << loader.errorString();
169 #endif
170 QCOMPARE(loaded, false);
171 QCOMPARE(loader.errorString(), unknown);
172
173 QObject *obj = loader.instance();
174 #ifdef SHOW_ERRORS
175 qDebug() << loader.errorString();
176 #endif
177 QCOMPARE(obj, static_cast<QObject*>(0));
178 QCOMPARE(loader.errorString(), unknown);
179
180 bool unloaded = loader.unload();
181 #ifdef SHOW_ERRORS
182 qDebug() << loader.errorString();
183 #endif
184 QCOMPARE(unloaded, false);
185 QCOMPARE(loader.errorString(), unknown);
186 }
187 {
188 QPluginLoader loader(qualifiedLibraryName(QLatin1String("tst_qpluginloaderlib"))); //not a plugin
189 bool loaded = loader.load();
190 #ifdef SHOW_ERRORS
191 qDebug() << loader.errorString();
192 #endif
193 QCOMPARE(loaded, false);
194 QVERIFY(loader.errorString() != unknown);
195
196 QObject *obj = loader.instance();
197 #ifdef SHOW_ERRORS
198 qDebug() << loader.errorString();
199 #endif
200 QCOMPARE(obj, static_cast<QObject*>(0));
201 QVERIFY(loader.errorString() != unknown);
202
203 bool unloaded = loader.unload();
204 #ifdef SHOW_ERRORS
205 qDebug() << loader.errorString();
206 #endif
207 QCOMPARE(unloaded, false);
208 QVERIFY(loader.errorString() != unknown);
209 }
210
211 {
212 QPluginLoader loader(qualifiedLibraryName(QLatin1String("nosuchfile"))); //not a file
213 bool loaded = loader.load();
214 #ifdef SHOW_ERRORS
215 qDebug() << loader.errorString();
216 #endif
217 QCOMPARE(loaded, false);
218 QVERIFY(loader.errorString() != unknown);
219
220 QObject *obj = loader.instance();
221 #ifdef SHOW_ERRORS
222 qDebug() << loader.errorString();
223 #endif
224 QCOMPARE(obj, static_cast<QObject*>(0));
225 QVERIFY(loader.errorString() != unknown);
226
227 bool unloaded = loader.unload();
228 #ifdef SHOW_ERRORS
229 qDebug() << loader.errorString();
230 #endif
231 QCOMPARE(unloaded, false);
232 QVERIFY(loader.errorString() != unknown);
233 }
234
235 #if !defined Q_OS_WIN && !defined Q_OS_MAC && !defined Q_OS_HPUX && !defined Q_OS_SYMBIAN && !defined Q_OS_QNX
236 {
237 QPluginLoader loader(qualifiedLibraryName(QLatin1String("almostplugin"))); //a plugin with unresolved symbols
238 loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
239 QCOMPARE(loader.load(), false);
240 #ifdef SHOW_ERRORS
241 qDebug() << loader.errorString();
242 #endif
243 QVERIFY(loader.errorString() != unknown);
244
245 QCOMPARE(loader.instance(), static_cast<QObject*>(0));
246 #ifdef SHOW_ERRORS
247 qDebug() << loader.errorString();
248 #endif
249 QVERIFY(loader.errorString() != unknown);
250
251 QCOMPARE(loader.unload(), false);
252 #ifdef SHOW_ERRORS
253 qDebug() << loader.errorString();
254 #endif
255 QVERIFY(loader.errorString() != unknown);
256 }
257 #endif
258
259 {
260 const QString plugin = qualifiedLibraryName(QLatin1String("theplugin"));
261 QPluginLoader loader(plugin); //a plugin
262 QVERIFY2(loader.load(), msgCannotLoadPlugin(plugin, loader.errorString()).constData());
263 QCOMPARE(loader.errorString(), unknown);
264
265 QVERIFY(loader.instance() != static_cast<QObject*>(0));
266 QCOMPARE(loader.errorString(), unknown);
267
268 // Make sure that plugin really works
269 PluginInterface* theplugin = qobject_cast<PluginInterface*>(loader.instance());
270 QString pluginName = theplugin->pluginName();
271 QCOMPARE(pluginName, QLatin1String("Plugin ok"));
272
273 QCOMPARE(loader.unload(), true);
274 QCOMPARE(loader.errorString(), unknown);
275 }
276 }
277
loadHints()278 void tst_QPluginLoader::loadHints()
279 {
280 QPluginLoader loader;
281 QCOMPARE(loader.loadHints(), (QLibrary::LoadHints)0); //Do not crash
282 loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
283 loader.setFileName(qualifiedLibraryName(QLatin1String("theplugin"))); //a plugin
284 QCOMPARE(loader.loadHints(), QLibrary::ResolveAllSymbolsHint);
285 }
286
deleteinstanceOnUnload()287 void tst_QPluginLoader::deleteinstanceOnUnload()
288 {
289 for (int pass = 0; pass < 4; ++pass) {
290 QPluginLoader loader1;
291 const QString plugin = qualifiedLibraryName(QLatin1String("theplugin"));
292 loader1.setFileName(plugin); //a plugin
293 if (pass < 2)
294 loader1.load(); // not recommended, instance() should do the job.
295 PluginInterface *instance1 = qobject_cast<PluginInterface*>(loader1.instance());
296 QVERIFY2(instance1, msgCannotLoadPlugin(plugin, loader1.errorString()).constData());
297 QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok"));
298
299 QPluginLoader loader2;
300 loader2.setFileName(qualifiedLibraryName(QLatin1String("theplugin"))); //a plugin
301 if (pass < 2)
302 loader2.load(); // not recommended, instance() should do the job.
303 PluginInterface *instance2 = qobject_cast<PluginInterface*>(loader2.instance());
304 QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok"));
305
306 QSignalSpy spy1(loader1.instance(), SIGNAL(destroyed()));
307 QSignalSpy spy2(loader2.instance(), SIGNAL(destroyed()));
308
309 // refcount not reached 0, not really unloaded
310 if (pass % 2)
311 QCOMPARE(loader1.unload(), false);
312 else
313 QCOMPARE(loader2.unload(), false);
314
315 QCOMPARE(spy1.count(), 0);
316 QCOMPARE(spy2.count(), 0);
317
318 QCOMPARE(instance1->pluginName(), QLatin1String("Plugin ok"));
319 QCOMPARE(instance2->pluginName(), QLatin1String("Plugin ok"));
320
321 // refcount reached 0, did really unload
322 if (pass % 2)
323 QVERIFY(loader2.unload());
324 else
325 QVERIFY(loader1.unload());
326
327 QCOMPARE(spy1.count(), 1);
328 QCOMPARE(spy2.count(), 1);
329 }
330 }
331
checkingStubsFromDifferentDrives()332 void tst_QPluginLoader::checkingStubsFromDifferentDrives()
333 {
334 #if defined(Q_OS_SYMBIAN)
335
336 // This test needs C-drive + some additional drive (driveForStubs)
337
338 const QString driveForStubs("E:/");// != "C:/"
339 const QString stubDir("system/temp/stubtest/");
340 const QString stubName("dummyStub.qtplugin");
341 const QString fullStubFileName(stubDir + stubName);
342 QDir dir(driveForStubs);
343 bool test1(false); bool test2(false);
344
345 // initial clean up
346 QFile::remove(driveForStubs + fullStubFileName);
347 dir.rmdir(driveForStubs + stubDir);
348
349 // create a stub dir and do stub drive check
350 if (!dir.mkpath(stubDir))
351 QSKIP("Required drive not available for this test", SkipSingle);
352
353 {// test without stub, should not be found
354 QPluginLoader loader("C:/" + fullStubFileName);
355 test1 = !loader.fileName().length();
356 }
357
358 // create a stub to defined drive
359 QFile tempFile(driveForStubs + fullStubFileName);
360 tempFile.open(QIODevice::ReadWrite);
361 QFileInfo fileInfo(tempFile);
362
363 {// now should be found even tried to find from C:
364 QPluginLoader loader("C:/" + fullStubFileName);
365 test2 = (loader.fileName() == fileInfo.absoluteFilePath());
366 }
367
368 // clean up
369 tempFile.close();
370 if (!QFile::remove(driveForStubs + fullStubFileName))
371 QWARN("Could not remove stub file");
372 if (!dir.rmdir(driveForStubs + stubDir))
373 QWARN("Could not remove stub directory");
374
375 // test after cleanup
376 QVERIFY(test1);
377 QVERIFY(test2);
378
379 #endif//Q_OS_SYMBIAN
380 }
381
loadDebugObj()382 void tst_QPluginLoader::loadDebugObj()
383 {
384 #ifdef __ELF__
385 const QString file = QString::fromLatin1(SRCDIR "elftest/debugobj.so");
386 QVERIFY(QFile::exists(file));
387 QPluginLoader lib1(file);
388 QCOMPARE(lib1.load(), false);
389 #else
390 QSKIP("Test requires __ELF", SkipAll);
391 #endif
392 }
393
394 /* loadCorruptElf() verifies that the library loader returns the correct error message.
395 * Note that Qt's plugin cache interferes here: on a fresh checkout, the "real" error
396 * message will be reported, since no cache exists or the timestamp is different. When
397 * run for the 2nd time, "not a valid Qt plugin" will be reported from the plugin cache.
398 * "Plugin verification data mismatch" has also been observed.
399 * This could arguably be fixed by copying the file to a temporary file each time, but
400 * that would grow the cache on the target machine. */
401
loadCorruptElf_data()402 void tst_QPluginLoader::loadCorruptElf_data()
403 {
404 #ifdef __ELF__
405 QTest::addColumn<QString>("file");
406 QTest::addColumn<QString>("error");
407 const QString folder = QLatin1String(SRCDIR "elftest/");
408 const QString invalidElfMessage = QLatin1String("is an invalid ELF object");
409 if (sizeof(void*) == 8) {
410 QTest::newRow("64bit-corrupt1")
411 << (folder + QLatin1String("corrupt1.elf64.so"))
412 << QString::fromLatin1("is not an ELF object");
413 QTest::newRow("64bit-corrupt2")
414 << (folder + QLatin1String("corrupt2.elf64.so")) << invalidElfMessage;
415 QTest::newRow("64bit-corrupt3")
416 << (folder + QLatin1String("corrupt3.elf64.so")) << invalidElfMessage;
417 } else if (sizeof(void*) == 4) {
418 QTest::newRow("32bit-corrupt3")
419 << (folder + QLatin1String("corrupt3.elf64.so"))
420 << QString::fromLatin1("architecture");
421 } else {
422 Q_ASSERT_X(false, Q_FUNC_INFO, "Please port QElfParser to this platform or blacklist this test.");
423 }
424 #endif
425 }
426
loadCorruptElf()427 void tst_QPluginLoader::loadCorruptElf()
428 {
429 #ifdef __ELF__
430 QFETCH(QString, file);
431 QFETCH(QString, error);
432
433 QVERIFY(QFile::exists(file));
434 QPluginLoader lib(file);
435 QCOMPARE(lib.load(), false);
436 const QString errorString = lib.errorString();
437 QVERIFY2(errorString.contains(error) || errorString.contains("not a valid Qt plugin") || errorString.contains("Plugin verification data mismatch"),
438 qPrintable(lib.errorString()));
439 #endif
440 }
441
loadGarbage()442 void tst_QPluginLoader::loadGarbage()
443 {
444 #if defined (Q_OS_UNIX) && !defined(Q_OS_SYMBIAN)
445 for (int i = 1; i <= 5; ++i) {
446 QPluginLoader lib(QString::fromLatin1(SRCDIR "elftest/garbage%1.so").arg(i));
447 QCOMPARE(lib.load(), false);
448 #ifdef SHOW_ERRORS
449 qDebug() << lib.errorString();
450 #endif
451 }
452 #endif
453 }
454
455 QTEST_APPLESS_MAIN(tst_QPluginLoader)
456 #include "tst_qpluginloader.moc"
457