Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
quicktest.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "quicktest_p.h"
7#include <QtTest/qtestsystem.h>
8#include <QtTest/private/qtestcrashhandler_p.h>
10#include <QtQml/qqml.h>
11#include <QtQml/qqmlengine.h>
12#include <QtQml/qqmlcontext.h>
13#include <QtQuick/private/qquickitem_p.h>
14#include <QtQuick/private/qquickwindow_p.h>
15#include <QtQuick/qquickitem.h>
16#include <QtQuick/qquickview.h>
17#include <QtQuick/qquickwindow.h>
18#include <QtQml/qjsvalue.h>
19#include <QtQml/qjsengine.h>
20#include <QtQml/qqmlpropertymap.h>
21#include <QtQuick/private/qquickitem_p.h>
22#include <QtQuick/qquickitem.h>
23#include <qopengl.h>
24#include <QtCore/qurl.h>
25#include <QtCore/qfileinfo.h>
26#include <QtCore/qdir.h>
27#include <QtCore/qdiriterator.h>
28#include <QtCore/qfile.h>
29#include <QtCore/qdebug.h>
30#include <QtCore/qeventloop.h>
31#include <QtCore/qtextstream.h>
32#include <QtCore/qtimer.h>
33#include <QtGui/qtextdocument.h>
34#include <QtGui/QGuiApplication>
35#include <QtGui/private/qguiapplication_p.h>
36#include <QtGui/qpa/qplatformintegration.h>
37#include <QtCore/QTranslator>
38#include <QtTest/QSignalSpy>
39#include <QtQml/QQmlFileSelector>
40
41#ifdef Q_OS_ANDROID
42# include <android/androidtestutils_p.h>
43#endif
44
45#include <private/qqmlcomponent_p.h>
46#include <private/qv4resolvedtypereference_p.h>
47
49
50/*!
51 \since 5.13
52
53 Returns \c true if \l {QQuickItem::}{updatePolish()} has not been called
54 on \a item since the last call to \l {QQuickItem::}{polish()},
55 otherwise returns \c false.
56
57 When assigning values to properties in QML, any layouting the item
58 must do as a result of the assignment might not take effect immediately,
59 but can instead be postponed until the item is polished. For these cases,
60 you can use this function to ensure that the item has been polished
61 before the execution of the test continues. For example:
62
63 \code
64 QVERIFY(QQuickTest::qIsPolishScheduled(item));
65 QVERIFY(QQuickTest::qWaitForItemPolished(item));
66 \endcode
67
68 Without the call to \c qIsPolishScheduled() above, the
69 call to \c qWaitForItemPolished() might see that no polish
70 was scheduled and therefore pass instantly, assuming that
71 the item had already been polished. This function
72 makes it obvious why an item wasn't polished and allows tests to
73 fail early under such circumstances.
74
75 The QML equivalent of this function is
76 \l {TestCase::}{isPolishScheduled()}.
77
78 \sa QQuickItem::polish(), QQuickItem::updatePolish()
79*/
80bool QQuickTest::qIsPolishScheduled(const QQuickItem *item)
81{
82 return QQuickItemPrivate::get(item)->polishScheduled;
83}
84
85/*!
86 \since 6.4
87 \overload qIsPolishScheduled()
88
89 Returns \c true if there are any items managed by this window for
90 which \c qIsPolishScheduled(item) returns \c true, otherwise
91 returns \c false.
92
93 For example, if an item somewhere within the scene may or may not
94 be polished, but you need to wait for it if it is, you can use
95 the following code:
96
97 \code
98 if (QQuickTest::qIsPolishScheduled(window))
99 QVERIFY(QQuickTest::qWaitForPolish(window));
100 \endcode
101
102 The QML equivalent of this function is
103 \l [QML]{TestCase::}{isPolishScheduled()}.
104
105 \sa QQuickItem::polish(), QQuickItem::updatePolish(),
106 QQuickTest::qWaitForPolish()
107*/
108bool QQuickTest::qIsPolishScheduled(const QQuickWindow *window)
109{
110 return !QQuickWindowPrivate::get(window)->itemsToPolish.isEmpty();
111}
112
113#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
114#if QT_DEPRECATED_SINCE(6, 4)
115/*!
116 \since 5.13
117 \deprecated [6.4] Use \l qWaitForPolish() instead.
118
119 Waits for \a timeout milliseconds or until
120 \l {QQuickItem::}{updatePolish()} has been called on \a item.
121
122 Returns \c true if \c updatePolish() was called on \a item within
123 \a timeout milliseconds, otherwise returns \c false.
124
125 The QML equivalent of this function is
126 \l {TestCase::}{waitForItemPolished()}.
127
128 \sa QQuickItem::polish(), QQuickItem::updatePolish(),
129 QQuickTest::qIsPolishScheduled()
130*/
131bool QQuickTest::qWaitForItemPolished(const QQuickItem *item, int timeout)
132{
133 return qWaitForPolish(item, timeout);
134}
135#endif
136#endif
137
138/*!
139 \since 6.4
140
141 Waits for \a timeout milliseconds or until
142 \l {QQuickItem::}{updatePolish()} has been called on \a item.
143
144 Returns \c true if \c updatePolish() was called on \a item within
145 \a timeout milliseconds, otherwise returns \c false.
146
147 \sa QQuickItem::polish(), QQuickItem::updatePolish(),
148 QQuickTest::qIsPolishScheduled()
149*/
150bool QQuickTest::qWaitForPolish(const QQuickItem *item, int timeout)
151{
152 return QTest::qWaitFor([&]() { return !QQuickItemPrivate::get(item)->polishScheduled; }, timeout);
153}
154
155/*!
156 \since 6.4
157
158 Waits for \a timeout milliseconds or until \c qIsPolishScheduled(item)
159 returns \c false for all items managed by \a window.
160
161 Returns \c true if \c qIsPolishScheduled(item) returns false for all items
162 within \a timeout milliseconds, otherwise returns \c false.
163
164 The QML equivalent of this function is
165 \l [QML]{TestCase::}{waitForPolish()}.
166
167 \sa QQuickItem::polish(), QQuickItem::updatePolish(),
168 QQuickTest::qIsPolishScheduled()
169*/
170bool QQuickTest::qWaitForPolish(const QQuickWindow *window, int timeout)
171{
172 return QTest::qWaitFor([&]() { return QQuickWindowPrivate::get(window)->itemsToPolish.isEmpty(); }, timeout);
173}
174
175static inline QString stripQuotes(const QString &s)
176{
177 if (s.size() >= 2 && s.startsWith(QLatin1Char('"')) && s.endsWith(QLatin1Char('"')))
178 return s.mid(1, s.size() - 2);
179 else
180 return s;
181}
182
184 const QFileInfo &fi, const QList<QQmlError> &errors, QQmlEngine *engine,
185 QQuickView *view = nullptr)
186{
187 // Error compiling the test - flag failure in the log and continue.
188 QuickTestResult results;
189 results.setTestCaseName(fi.baseName());
190 results.startLogging();
191 results.setFunctionName(QLatin1String("compile"));
192 // Verbose warning output of all messages and relevant parameters
193 QString message;
194 QTextStream str(&message);
195 str << "\n " << QDir::toNativeSeparators(fi.absoluteFilePath()) << " produced "
196 << errors.size() << " error(s):\n";
197 for (const QQmlError &e : errors) {
198 str << " ";
199 if (e.url().isLocalFile()) {
200 str << QDir::toNativeSeparators(e.url().toLocalFile());
201 } else {
202 str << e.url().toString();
203 }
204 if (e.line() > 0)
205 str << ':' << e.line() << ',' << e.column();
206 str << ": " << e.description() << '\n';
207 }
208 str << " Working directory: " << QDir::toNativeSeparators(QDir::current().absolutePath()) << '\n';
209 if (engine) {
210 str << " ";
211 if (view)
212 str << "View: " << view->metaObject()->className() << ", ";
213 str << "Import paths:\n";
214 const auto importPaths = engine->importPathList();
215 for (const QString &i : importPaths)
216 str << " '" << QDir::toNativeSeparators(i) << "'\n";
217 const QStringList pluginPaths = engine->pluginPathList();
218 str << " Plugin paths:\n";
219 for (const QString &p : pluginPaths)
220 str << " '" << QDir::toNativeSeparators(p) << "'\n";
221 }
222 qWarning("%s", qPrintable(message));
223 // Fail with error 0.
224 results.fail(errors.at(0).description(),
225 errors.at(0).url(), errors.at(0).line());
226 results.finishTestData();
227 results.finishTestDataCleanup();
228 results.finishTestFunction();
229 results.setFunctionName(QString());
230 results.stopLogging();
231}
232
233class SimpleReceiver : public QObject {
235public:
236 bool signalReceived = false;
237public slots:
238 void slotFun() { signalReceived = true; }
239};
240
241bool qWaitForSignal(QObject *obj, const char* signal, int timeout)
242{
243 if (!obj || !signal) {
244 qWarning("qWaitForSignal: invalid arguments");
245 return false;
246 }
247 if (((signal[0] - '0') & 0x03) != QSIGNAL_CODE) {
248 qWarning("qWaitForSignal: not a valid signal, use the SIGNAL macro");
249 return false;
250 }
251
252 int sig = obj->metaObject()->indexOfSignal(signal + 1);
253 if (sig == -1) {
254 const QByteArray ba = QMetaObject::normalizedSignature(signal + 1);
255 sig = obj->metaObject()->indexOfSignal(ba.constData());
256 if (sig == -1) {
257 qWarning("qWaitForSignal: no such signal %s::%s", obj->metaObject()->className(),
258 signal);
259 return false;
260 }
261 }
262
263 SimpleReceiver receiver;
264 static int slot = receiver.metaObject()->indexOfSlot("slotFun()");
265 if (!QMetaObject::connect(obj, sig, &receiver, slot)) {
266 qWarning("qWaitForSignal: failed to connect to signal %s::%s",
267 obj->metaObject()->className(), signal);
268 return false;
269 }
270
271 return QTest::qWaitFor([&]() { return receiver.signalReceived; }, timeout);
272}
273
274template <typename... Args>
275void maybeInvokeSetupMethod(QObject *setupObject, const char *member, Args &&... args)
276{
277 // It's OK if it doesn't exist: since we have more than one callback that
278 // can be called, it makes sense if the user only implements one of them.
279 // We do this the long way rather than just calling the static
280 // QMetaObject::invokeMethod(), because that will issue a warning if the
281 // function doesn't exist, which we don't want.
282 const QMetaObject *setupMetaObject = setupObject->metaObject();
283 const int methodIndex = setupMetaObject->indexOfMethod(member);
284 if (methodIndex != -1) {
285 const QMetaMethod method = setupMetaObject->method(methodIndex);
286 method.invoke(setupObject, std::forward<Args>(args)...);
287 }
288}
289
290using namespace QV4::CompiledData;
291
293{
294public:
296
297 TestCaseCollector(const QFileInfo &fileInfo, QQmlEngine *engine) : m_engine(engine)
298 {
299 QString path = fileInfo.absoluteFilePath();
300 if (path.startsWith(QLatin1String(":/")))
301 path.prepend(QLatin1String("qrc"));
302
303 QQmlComponent component(engine, path);
304 m_errors += component.errors();
305
306 if (component.isReady()) {
307 QQmlRefPointer<QV4::ExecutableCompilationUnit> rootCompilationUnit
308 = QQmlComponentPrivate::get(&component)->compilationUnit();
309 TestCaseEnumerationResult result = enumerateTestCases(
310 rootCompilationUnit->baseCompilationUnit().data());
311 m_testCases = result.testCases + result.finalizedPartialTestCases();
312 m_errors += result.errors;
313 }
314 }
315
316 TestCaseList testCases() const { return m_testCases; }
317 QList<QQmlError> errors() const { return m_errors; }
318
319private:
320 TestCaseList m_testCases;
321 QList<QQmlError> m_errors;
322 QQmlEngine *m_engine = nullptr;
323
324 struct TestCaseEnumerationResult
325 {
326 TestCaseList testCases;
327 QList<QQmlError> errors;
328
329 // Partially constructed test cases
330 bool isTestCase = false;
331 TestCaseList testFunctions;
332 QString testCaseName;
333
334 TestCaseList finalizedPartialTestCases() const
335 {
336 TestCaseList result;
337 for (const QString &function : testFunctions)
338 result << QString(QStringLiteral("%1::%2")).arg(testCaseName).arg(function);
339 return result;
340 }
341
342 TestCaseEnumerationResult &operator<<(const TestCaseEnumerationResult &other)
343 {
344 testCases += other.testCases + other.finalizedPartialTestCases();
345 errors += other.errors;
346 return *this;
347 }
348 };
349
350 TestCaseEnumerationResult enumerateTestCases(
351 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
352 const QV4::CompiledData::Object *object = nullptr)
353 {
354 QQmlType testCaseType;
355 for (quint32 i = 0, count = compilationUnit->importCount(); i < count; ++i) {
356 const Import *import = compilationUnit->importAt(i);
357 if (compilationUnit->stringAt(import->uriIndex) != QLatin1String("QtTest"))
358 continue;
359
360 QString testCaseTypeName(QStringLiteral("TestCase"));
361 QString typeQualifier = compilationUnit->stringAt(import->qualifierIndex);
362 if (!typeQualifier.isEmpty())
363 testCaseTypeName = typeQualifier % QLatin1Char('.') % testCaseTypeName;
364
365 testCaseType = compilationUnit->typeNameCache->query(
366 testCaseTypeName, QQmlTypeLoader::get(m_engine)).type;
367 if (testCaseType.isValid())
368 break;
369 }
370
371 TestCaseEnumerationResult result;
372
373 if (!object) // Start at root of compilation unit if not enumerating a specific child
374 object = compilationUnit->objectAt(0);
375 if (object->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot))
376 return result;
377
378 if (const auto superType
379 = compilationUnit->resolvedType(object->inheritedTypeNameIndex)->type();
380 superType.isComposite() || superType.isInlineComponentType()) {
381
382 // Reset fragment, so that we don't run into problems with inline components
383 QUrl baseUrl = superType.sourceUrl();
384 baseUrl.setFragment(QString());
385
386 // We have a non-C++ super type, which could indicate we're a subtype of a TestCase
387 if (testCaseType.isValid() && baseUrl == testCaseType.sourceUrl()) {
388 result.isTestCase = true;
389 } else if (baseUrl != compilationUnit->url()) {
390 // base urls are the same for inline component, avoid infinite recursion
391 result = enumerateTestCases(QQmlMetaType::obtainCompilationUnit(baseUrl));
392 }
393
394 if (result.isTestCase) {
395 // Look for override of name in this type
396 for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) {
397 if (compilationUnit->stringAt(binding->propertyNameIndex) == QLatin1String("name")) {
398 if (binding->type() == QV4::CompiledData::Binding::Type_String) {
399 result.testCaseName = compilationUnit->stringAt(binding->stringIndex);
400 } else {
401 QQmlError error;
402 error.setUrl(compilationUnit->url());
403 error.setLine(binding->location.line());
404 error.setColumn(binding->location.column());
405 error.setDescription(QStringLiteral("the 'name' property of a TestCase must be a literal string"));
406 result.errors << error;
407 }
408 break;
409 }
410 }
411
412 // Look for additional functions in this type
413 auto functionsEnd = compilationUnit->objectFunctionsEnd(object);
414 for (auto function = compilationUnit->objectFunctionsBegin(object); function != functionsEnd; ++function) {
415 QString functionName = compilationUnit->stringAt(function->nameIndex);
416 if (!(functionName.startsWith(QLatin1String("test_")) || functionName.startsWith(QLatin1String("benchmark_"))))
417 continue;
418
419 if (functionName.endsWith(QLatin1String("_data")))
420 continue;
421
422 result.testFunctions << functionName;
423 }
424 }
425 }
426
427 for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) {
428 if (binding->type() == QV4::CompiledData::Binding::Type_Object) {
429 const QV4::CompiledData::Object *child = compilationUnit->objectAt(binding->value.objectIndex);
430 result << enumerateTestCases(compilationUnit, child);
431 }
432 }
433
434 return result;
435 }
436};
437
438int quick_test_main(int argc, char **argv, const char *name, const char *sourceDir)
439{
440 return quick_test_main_with_setup(argc, argv, name, sourceDir, nullptr);
441}
442
443int quick_test_main_with_setup(int argc, char **argv, const char *name, const char *sourceDir, QObject *setup)
444{
445 QScopedPointer<QCoreApplication> app;
446 if (!QCoreApplication::instance())
447 app.reset(new QGuiApplication(argc, argv));
448
449#ifdef Q_OS_ANDROID
450 clearAndroidExitCode();
451#endif
452
453 if (setup)
454 maybeInvokeSetupMethod(setup, "applicationAvailable()");
455
456 // Look for QML-specific command-line options.
457 // -import dir Specify an import directory.
458 // -plugins dir Specify a directory where to search for plugins.
459 // -input dir Specify the input directory for test cases.
460 // -translation file Specify the translation file.
461 // -file-selector Specify a file selector
462 QStringList imports;
463 QStringList pluginPaths;
464 QString testPath;
465 QString translationFile;
466 QStringList fileSelectors;
467 int index = 1;
468 QScopedArrayPointer<char *> testArgV(new char *[argc + 1]);
469 testArgV[0] = argv[0];
470 int testArgC = 1;
471 while (index < argc) {
472 if (strcmp(argv[index], "-import") == 0 && (index + 1) < argc) {
473 imports += stripQuotes(QString::fromLocal8Bit(argv[index + 1]));
474 index += 2;
475 } else if (strcmp(argv[index], "-plugins") == 0 && (index + 1) < argc) {
476 pluginPaths += stripQuotes(QString::fromLocal8Bit(argv[index + 1]));
477 index += 2;
478 } else if (strcmp(argv[index], "-input") == 0 && (index + 1) < argc) {
479 testPath = stripQuotes(QString::fromLocal8Bit(argv[index + 1]));
480 index += 2;
481 } else if (strcmp(argv[index], "-opengl") == 0) {
482 ++index;
483 } else if (strcmp(argv[index], "-translation") == 0 && (index + 1) < argc) {
484 translationFile = stripQuotes(QString::fromLocal8Bit(argv[index + 1]));
485 index += 2;
486 } else if (strcmp(argv[index], "-file-selector") == 0 && (index + 1) < argc) {
487 fileSelectors += stripQuotes(QString::fromLocal8Bit(argv[index + 1]));
488 index += 2;
489 } else {
490 testArgV[testArgC++] = argv[index++];
491 }
492 }
493 testArgV[testArgC] = 0;
494
495 // Setting currentAppname and currentTestObjectName (via setProgramName) are needed
496 // for the code coverage analysis. Must be done before parseArgs is called.
497 QuickTestResult::setCurrentAppname(argv[0]);
498 QuickTestResult::setProgramName(name);
499
500 QuickTestResult::parseArgs(testArgC, testArgV.data());
501
502#if QT_CONFIG(translation)
503 QTranslator translator;
504 if (!translationFile.isEmpty()) {
505 if (translator.load(translationFile)) {
506 app->installTranslator(&translator);
507 } else {
508 qWarning("Could not load the translation file '%s'.", qPrintable(translationFile));
509 }
510 }
511#endif
512
513 // Determine where to look for the test data.
514 if (testPath.isEmpty() && sourceDir) {
515 const QString s = QString::fromLocal8Bit(sourceDir);
516 if (QFile::exists(s))
517 testPath = s;
518 }
519
520#if defined(Q_OS_ANDROID) || defined(Q_OS_INTEGRITY)
521 if (testPath.isEmpty())
522 testPath = QLatin1String(":/");
523#endif
524
525 if (testPath.isEmpty()) {
526 QDir current = QDir::current();
527#ifdef Q_OS_WIN
528 // Skip release/debug subfolders
529 if (!current.dirName().compare(QLatin1String("Release"), Qt::CaseInsensitive)
530 || !current.dirName().compare(QLatin1String("Debug"), Qt::CaseInsensitive))
531 current.cdUp();
532#endif // Q_OS_WIN
533 testPath = current.absolutePath();
534 }
535 QStringList files;
536
537 const QFileInfo testPathInfo(testPath);
538 if (testPathInfo.isFile()) {
539 if (testPath.endsWith(QLatin1String(".qml"))) {
540 files << testPath;
541 } else if (testPath.endsWith(QLatin1String(".qmltests"))) {
542 QFile file(testPath);
543 if (file.open(QIODevice::ReadOnly)) {
544 while (!file.atEnd()) {
545 const QString filePath = testPathInfo.dir()
546 .filePath(QString::fromUtf8(file.readLine()))
547 .trimmed();
548 const QFileInfo f(filePath);
549 if (f.exists())
550 files.append(filePath);
551 else
552 qWarning("The test file '%s' does not exists", qPrintable(filePath));
553 }
554 file.close();
555 files.sort();
556 if (files.isEmpty()) {
557 qWarning("The file '%s' does not contain any tests files",
558 qPrintable(testPath));
559 return 1;
560 }
561 } else {
562 qWarning("Could not read '%s'", qPrintable(testPath));
563 }
564 } else {
565 qWarning("'%s' does not have the suffix '.qml' or '.qmltests'.", qPrintable(testPath));
566 return 1;
567 }
568 } else if (testPathInfo.isDir()) {
569 // Scan the test data directory recursively, looking for "tst_*.qml" files.
570 const QStringList filters(QStringLiteral("tst_*.qml"));
571 QDirIterator iter(testPathInfo.absoluteFilePath(), filters, QDir::Files,
572 QDirIterator::Subdirectories |
573 QDirIterator::FollowSymlinks);
574 while (iter.hasNext())
575 files += iter.next();
576 files.sort();
577 if (files.isEmpty()) {
578 qWarning("The directory '%s' does not contain any test files matching '%s'",
579 qPrintable(testPath), qPrintable(filters.front()));
580 return 1;
581 }
582 } else {
583 qWarning("'%s' does not exist under '%s'.",
584 qPrintable(testPath), qPrintable(QDir::currentPath()));
585 return 1;
586 }
587
588 std::optional<QTest::CrashHandler::FatalSignalHandler> handler;
589 QTest::CrashHandler::prepareStackTrace();
590 if (!QTest::Internal::noCrashHandler)
591 handler.emplace();
592
593 qputenv("QT_QTESTLIB_RUNNING", "1");
594
595 QSet<QString> commandLineTestFunctions(QTest::testFunctions.cbegin(), QTest::testFunctions.cend());
596 const bool filteringTestFunctions = !commandLineTestFunctions.isEmpty();
597
598 // Scan through all of the "tst_*.qml" files and run each of them
599 // in turn with a separate QQuickView (for test isolation).
600 for (const QString &file : std::as_const(files)) {
601 const QFileInfo fi(file);
602 if (!fi.exists())
603 continue;
604
605 QQmlEngine engine;
606 for (const QString &path : std::as_const(imports))
607 engine.addImportPath(path);
608 for (const QString &path : std::as_const(pluginPaths))
609 engine.addPluginPath(path);
610
611 if (!fileSelectors.isEmpty()) {
612 QQmlFileSelector* const qmlFileSelector = new QQmlFileSelector(&engine, &engine);
613 qmlFileSelector->setExtraSelectors(fileSelectors);
614 }
615
616 // Do this down here so that import paths, plugin paths, file selectors, etc. are available
617 // in case the user needs access to them. Do it _before_ the TestCaseCollector parses the
618 // QML files though, because it attempts to import modules, which might not be available
619 // if qmlRegisterType()/QQmlEngine::addImportPath() are called in qmlEngineAvailable().
620 if (setup)
621 maybeInvokeSetupMethod(setup, "qmlEngineAvailable(QQmlEngine*)", Q_ARG(QQmlEngine*, &engine));
622
623 TestCaseCollector testCaseCollector(fi, &engine);
624 if (!testCaseCollector.errors().isEmpty()) {
625 handleCompileErrors(fi, testCaseCollector.errors(), &engine);
626 continue;
627 }
628
629 TestCaseCollector::TestCaseList availableTestFunctions = testCaseCollector.testCases();
630 if (QTest::printAvailableFunctions) {
631 for (const QString &function : availableTestFunctions)
632 qDebug("%s()", qPrintable(function));
633 continue;
634 }
635
636 const QSet<QString> availableTestSet(availableTestFunctions.cbegin(), availableTestFunctions.cend());
637 if (filteringTestFunctions && !availableTestSet.intersects(commandLineTestFunctions))
638 continue;
639 commandLineTestFunctions.subtract(availableTestSet);
640
641 QQuickView view(&engine, nullptr);
642 view.setFlags(Qt::Window | Qt::WindowSystemMenuHint
643 | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint
644 | Qt::WindowCloseButtonHint);
645 QEventLoop eventLoop;
646 QObject::connect(view.engine(), SIGNAL(quit()),
647 QTestRootObject::instance(), SLOT(quit()));
648 QObject::connect(view.engine(), SIGNAL(quit()),
649 &eventLoop, SLOT(quit()));
650 view.rootContext()->setContextProperty
651 (QLatin1String("qtest"), QTestRootObject::instance()); // Deprecated. Use QTestRootObject from QtTest instead
652
653 view.setObjectName(fi.baseName());
654 view.setTitle(view.objectName());
655 QTestRootObject::instance()->init();
656 QString path = fi.absoluteFilePath();
657 if (path.startsWith(QLatin1String(":/")))
658 view.setSource(QUrl(QLatin1String("qrc:") + QStringView{path}.mid(1)));
659 else
660 view.setSource(QUrl::fromLocalFile(path));
661
662 while (view.status() == QQuickView::Loading)
663 QTest::qWait(10);
664 if (view.status() == QQuickView::Error) {
665 handleCompileErrors(fi, view.errors(), view.engine(), &view);
666 continue;
667 }
668
669 view.setFramePosition(QPoint(50, 50));
670 if (view.size().isEmpty()) { // Avoid hangs with empty windows.
671 view.resize(200, 200);
672 }
673 view.show();
674 if (!QTest::qWaitForWindowExposed(&view)) {
675 qWarning().nospace()
676 << "Test '" << QDir::toNativeSeparators(path) << "' window not exposed after show().";
677 }
678 if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) {
679 view.requestActivate();
680 if (!QTest::qWaitForWindowActive(&view)) {
681 qWarning().nospace()
682 << "Test '" << QDir::toNativeSeparators(path) << "' window not active after requestActivate().";
683 }
684 }
685 if (view.isExposed()) {
686 // Defer property update until event loop has started
687 QTimer::singleShot(0, []() {
688 QTestRootObject::instance()->setWindowShown(true);
689 });
690 } else {
691 qWarning().nospace()
692 << "Test '" << QDir::toNativeSeparators(path) << "' window was never exposed! "
693 << "If the test case was expecting windowShown, it will hang.";
694 }
695 if (!QTestRootObject::instance()->hasQuit && QTestRootObject::instance()->hasTestCase())
696 eventLoop.exec();
697 }
698
699 if (setup)
700 maybeInvokeSetupMethod(setup, "cleanupTestCase()");
701
702 // Flush the current logging stream.
703 QuickTestResult::setProgramName(nullptr);
704 app.reset();
705
706 // Check that all test functions passed on the command line were found
707 if (!commandLineTestFunctions.isEmpty()) {
708 qWarning() << "Could not find the following test functions:";
709 for (const QString &functionName : std::as_const(commandLineTestFunctions))
710 qWarning(" %s()", qUtf8Printable(functionName));
711 return commandLineTestFunctions.size();
712 }
713
714 const int exitCode = QuickTestResult::exitCode();
715
716#ifdef Q_OS_ANDROID
717 writeAndroidExitCode(exitCode);
718#endif
719
720 // Return the number of failures as the exit code.
721 return exitCode;
722}
723
724/*!
725 \macro QVERIFY_ACTIVE_FOCUS(item)
726 \relates QQuickTest
727 \since 6.10
728
729 Checks whether the \a item (which must be derived from \l QQuickItem) has
730 \l {QQuickItem::activeFocus}{active focus}. If it does, execution
731 continues. If not, a failure is recorded in the test log and the test won't
732 be executed further. The failure message contains focus-specific
733 information that is relevant for diagnosing the cause of the failure.
734
735 \include macro-usage-limitation.qdocinc
736*/
737
738/*!
739 \macro QTRY_VERIFY_ACTIVE_FOCUS(item)
740 \relates QQuickTest
741 \since 6.10
742
743 This macro does the same check as \l QVERIFY_ACTIVE_FOCUS with \a item, but
744 repeatedly, until either the condition becomes true or the timeout (in
745 milliseconds) is reached. Between each evaluation, events will be
746 processed. If the timeout is reached, a failure is recorded in the test log
747 and the test won't be executed further.
748
749 \include macro-usage-limitation.qdocinc
750*/
751
752QByteArray QQuickTest::Private::qActiveFocusFailureMessage(const QQuickItem *item)
753{
754 QByteArray message;
755 QDebug debug(&message);
756 const QQuickWindow *window = item->window();
757 const QString activeFocusItemStr = window
758 ? QDebug::toString(window->activeFocusItem()) : QStringLiteral("(unknown; item has no window)");
759 debug.nospace() << "item: " << item
760 << " focusPolicy: " << static_cast<Qt::FocusPolicy>(item->focusPolicy());
761 debug.noquote() << " window's activeFocusItem: " << activeFocusItemStr;
762 return message;
763}
764
765QT_END_NAMESPACE
766
767#include "moc_quicktest_p.cpp"
768#include "quicktest.moc"
QList< QString > TestCaseList
QList< QQmlError > errors() const
TestCaseList testCases() const
TestCaseCollector(const QFileInfo &fileInfo, QQmlEngine *engine)
bool qWaitForSignal(QObject *obj, const char *signal, int timeout)
static QString stripQuotes(const QString &s)
int quick_test_main(int argc, char **argv, const char *name, const char *sourceDir)
static void handleCompileErrors(const QFileInfo &fi, const QList< QQmlError > &errors, QQmlEngine *engine, QQuickView *view=nullptr)
void maybeInvokeSetupMethod(QObject *setupObject, const char *member, Args &&... args)
int quick_test_main_with_setup(int argc, char **argv, const char *name, const char *sourceDir, QObject *setup)