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

source code of qtdeclarative/src/qmltest/quicktest.cpp