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