1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://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 https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://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 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "quicktest_p.h"
41#include "quicktestresult_p.h"
42#include <QtTest/qtestsystem.h>
43#include "qtestoptions_p.h"
44#include <QtQml/qqml.h>
45#include <QtQml/qqmlengine.h>
46#include <QtQml/qqmlcontext.h>
47#include <QtQuick/private/qquickitem_p.h>
48#include <QtQuick/qquickitem.h>
49#include <QtQuick/qquickview.h>
50#include <QtQml/qjsvalue.h>
51#include <QtQml/qjsengine.h>
52#include <QtQml/qqmlpropertymap.h>
53#include <QtQuick/private/qquickitem_p.h>
54#include <QtQuick/qquickitem.h>
55#include <QtGui/qopengl.h>
56#include <QtCore/qurl.h>
57#include <QtCore/qfileinfo.h>
58#include <QtCore/qdir.h>
59#include <QtCore/qdiriterator.h>
60#include <QtCore/qfile.h>
61#include <QtCore/qdebug.h>
62#include <QtCore/qeventloop.h>
63#include <QtCore/qtextstream.h>
64#include <QtCore/qtimer.h>
65#include <QtGui/qtextdocument.h>
66#include <stdio.h>
67#include <QtGui/QGuiApplication>
68#include <QtCore/QTranslator>
69#include <QtTest/QSignalSpy>
70#include <QtQml/QQmlFileSelector>
71
72#include <private/qqmlcomponent_p.h>
73#include <private/qv4executablecompilationunit_p.h>
74
75#ifdef QT_QMLTEST_WITH_WIDGETS
76#include <QtWidgets/QApplication>
77#endif
78
79QT_BEGIN_NAMESPACE
80
81/*!
82 \since 5.13
83
84 Returns \c true if \l {QQuickItem::}{updatePolish()} has not been called
85 on \a item since the last call to \l {QQuickItem::}{polish()},
86 otherwise returns \c false.
87
88 When assigning values to properties in QML, any layouting the item
89 must do as a result of the assignment might not take effect immediately,
90 but can instead be postponed until the item is polished. For these cases,
91 you can use this function to ensure that the item has been polished
92 before the execution of the test continues. For example:
93
94 \code
95 QVERIFY(QQuickTest::qIsPolishScheduled(item));
96 QVERIFY(QQuickTest::qWaitForItemPolished(item));
97 \endcode
98
99 Without the call to \c qIsPolishScheduled() above, the
100 call to \c qWaitForItemPolished() might see that no polish
101 was scheduled and therefore pass instantly, assuming that
102 the item had already been polished. This function
103 makes it obvious why an item wasn't polished and allows tests to
104 fail early under such circumstances.
105
106 The QML equivalent of this function is
107 \l {TestCase::}{isPolishScheduled()}.
108
109 \sa QQuickItem::polish(), QQuickItem::updatePolish()
110*/
111bool QQuickTest::qIsPolishScheduled(const QQuickItem *item)
112{
113 return QQuickItemPrivate::get(item)->polishScheduled;
114}
115
116/*!
117 \since 5.13
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 QTest::qWaitFor(predicate: [&]() { return !QQuickItemPrivate::get(item)->polishScheduled; }, timeout);
134}
135
136static QObject *testRootObject(QQmlEngine *engine, QJSEngine *jsEngine)
137{
138 Q_UNUSED(engine);
139 Q_UNUSED(jsEngine);
140 return QTestRootObject::instance();
141}
142
143static inline QString stripQuotes(const QString &s)
144{
145 if (s.length() >= 2 && s.startsWith(c: QLatin1Char('"')) && s.endsWith(c: QLatin1Char('"')))
146 return s.mid(position: 1, n: s.length() - 2);
147 else
148 return s;
149}
150
151void handleCompileErrors(const QFileInfo &fi, QQuickView *view)
152{
153 // Error compiling the test - flag failure in the log and continue.
154 const QList<QQmlError> errors = view->errors();
155 QuickTestResult results;
156 results.setTestCaseName(fi.baseName());
157 results.startLogging();
158 results.setFunctionName(QLatin1String("compile"));
159 // Verbose warning output of all messages and relevant parameters
160 QString message;
161 QTextStream str(&message);
162 str << "\n " << QDir::toNativeSeparators(pathName: fi.absoluteFilePath()) << " produced "
163 << errors.size() << " error(s):\n";
164 for (const QQmlError &e : errors) {
165 str << " ";
166 if (e.url().isLocalFile()) {
167 str << QDir::toNativeSeparators(pathName: e.url().toLocalFile());
168 } else {
169 str << e.url().toString();
170 }
171 if (e.line() > 0)
172 str << ':' << e.line() << ',' << e.column();
173 str << ": " << e.description() << '\n';
174 }
175 str << " Working directory: " << QDir::toNativeSeparators(pathName: QDir::current().absolutePath()) << '\n';
176 if (QQmlEngine *engine = view->engine()) {
177 str << " View: " << view->metaObject()->className() << ", import paths:\n";
178 const auto importPaths = engine->importPathList();
179 for (const QString &i : importPaths)
180 str << " '" << QDir::toNativeSeparators(pathName: i) << "'\n";
181 const QStringList pluginPaths = engine->pluginPathList();
182 str << " Plugin paths:\n";
183 for (const QString &p : pluginPaths)
184 str << " '" << QDir::toNativeSeparators(pathName: p) << "'\n";
185 }
186 qWarning(msg: "%s", qPrintable(message));
187 // Fail with error 0.
188 results.fail(message: errors.at(i: 0).description(),
189 location: errors.at(i: 0).url(), line: errors.at(i: 0).line());
190 results.finishTestData();
191 results.finishTestDataCleanup();
192 results.finishTestFunction();
193 results.setFunctionName(QString());
194 results.stopLogging();
195}
196
197bool qWaitForSignal(QObject *obj, const char* signal, int timeout = 5000)
198{
199 QSignalSpy spy(obj, signal);
200 QElapsedTimer timer;
201 timer.start();
202
203 while (!spy.size()) {
204 int remaining = timeout - int(timer.elapsed());
205 if (remaining <= 0)
206 break;
207 QCoreApplication::processEvents(flags: QEventLoop::AllEvents, maxtime: remaining);
208 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
209 QTest::qSleep(ms: 10);
210 }
211
212 return spy.size();
213}
214
215void maybeInvokeSetupMethod(QObject *setupObject, const char *member, QGenericArgument val0 = QGenericArgument(nullptr))
216{
217 // It's OK if it doesn't exist: since we have more than one callback that
218 // can be called, it makes sense if the user only implements one of them.
219 // We do this the long way rather than just calling the static
220 // QMetaObject::invokeMethod(), because that will issue a warning if the
221 // function doesn't exist, which we don't want.
222 const QMetaObject *setupMetaObject = setupObject->metaObject();
223 const int methodIndex = setupMetaObject->indexOfMethod(method: member);
224 if (methodIndex != -1) {
225 const QMetaMethod method = setupMetaObject->method(index: methodIndex);
226 method.invoke(object: setupObject, val0);
227 }
228}
229
230using namespace QV4::CompiledData;
231
232class TestCaseCollector
233{
234public:
235 typedef QList<QString> TestCaseList;
236
237 TestCaseCollector(const QFileInfo &fileInfo, QQmlEngine *engine)
238 {
239 QString path = fileInfo.absoluteFilePath();
240 if (path.startsWith(s: QLatin1String(":/")))
241 path.prepend(s: QLatin1String("qrc"));
242
243 QQmlComponent component(engine, path);
244 m_errors += component.errors();
245
246 if (component.isReady()) {
247 QQmlRefPointer<QV4::ExecutableCompilationUnit> rootCompilationUnit
248 = QQmlComponentPrivate::get(c: &component)->compilationUnit;
249 TestCaseEnumerationResult result = enumerateTestCases(compilationUnit: rootCompilationUnit.data());
250 m_testCases = result.testCases + result.finalizedPartialTestCases();
251 m_errors += result.errors;
252 }
253 }
254
255 TestCaseList testCases() const { return m_testCases; }
256 QList<QQmlError> errors() const { return m_errors; }
257
258private:
259 TestCaseList m_testCases;
260 QList<QQmlError> m_errors;
261
262 struct TestCaseEnumerationResult
263 {
264 TestCaseList testCases;
265 QList<QQmlError> errors;
266
267 // Partially constructed test cases
268 bool isTestCase = false;
269 TestCaseList testFunctions;
270 QString testCaseName;
271
272 TestCaseList finalizedPartialTestCases() const
273 {
274 TestCaseList result;
275 for (const QString &function : testFunctions)
276 result << QString(QStringLiteral("%1::%2")).arg(a: testCaseName).arg(a: function);
277 return result;
278 }
279
280 TestCaseEnumerationResult &operator<<(const TestCaseEnumerationResult &other)
281 {
282 testCases += other.testCases + other.finalizedPartialTestCases();
283 errors += other.errors;
284 return *this;
285 }
286 };
287
288 TestCaseEnumerationResult enumerateTestCases(
289 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
290 const Object *object = nullptr)
291 {
292 QQmlType testCaseType;
293 for (quint32 i = 0, count = compilationUnit->importCount(); i < count; ++i) {
294 const Import *import = compilationUnit->importAt(index: i);
295 if (compilationUnit->stringAt(index: import->uriIndex) != QLatin1String("QtTest"))
296 continue;
297
298 QString testCaseTypeName(QStringLiteral("TestCase"));
299 QString typeQualifier = compilationUnit->stringAt(index: import->qualifierIndex);
300 if (!typeQualifier.isEmpty())
301 testCaseTypeName = typeQualifier % QLatin1Char('.') % testCaseTypeName;
302
303 testCaseType = compilationUnit->typeNameCache->query(testCaseTypeName).type;
304 if (testCaseType.isValid())
305 break;
306 }
307
308 TestCaseEnumerationResult result;
309
310 if (!object) // Start at root of compilation unit if not enumerating a specific child
311 object = compilationUnit->objectAt(index: 0);
312 if (object->flags & Object::IsInlineComponentRoot)
313 return result;
314
315 if (const auto superTypeUnit = compilationUnit->resolvedTypes.value(
316 akey: object->inheritedTypeNameIndex)->compilationUnit()) {
317 // We have a non-C++ super type, which could indicate we're a subtype of a TestCase
318 if (testCaseType.isValid() && superTypeUnit->url() == testCaseType.sourceUrl())
319 result.isTestCase = true;
320 else if (superTypeUnit->url() != compilationUnit->url()) { // urls are the same for inline component, avoid infinite recursion
321 result = enumerateTestCases(compilationUnit: superTypeUnit);
322 }
323
324 if (result.isTestCase) {
325 // Look for override of name in this type
326 for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) {
327 if (compilationUnit->stringAt(index: binding->propertyNameIndex) == QLatin1String("name")) {
328 if (binding->type == QV4::CompiledData::Binding::Type_String) {
329 result.testCaseName = compilationUnit->stringAt(index: binding->stringIndex);
330 } else {
331 QQmlError error;
332 error.setUrl(compilationUnit->url());
333 error.setLine(binding->location.line);
334 error.setColumn(binding->location.column);
335 error.setDescription(QStringLiteral("the 'name' property of a TestCase must be a literal string"));
336 result.errors << error;
337 }
338 break;
339 }
340 }
341
342 // Look for additional functions in this type
343 auto functionsEnd = compilationUnit->objectFunctionsEnd(object);
344 for (auto function = compilationUnit->objectFunctionsBegin(object); function != functionsEnd; ++function) {
345 QString functionName = compilationUnit->stringAt(index: function->nameIndex);
346 if (!(functionName.startsWith(s: QLatin1String("test_")) || functionName.startsWith(s: QLatin1String("benchmark_"))))
347 continue;
348
349 if (functionName.endsWith(s: QLatin1String("_data")))
350 continue;
351
352 result.testFunctions << functionName;
353 }
354 }
355 }
356
357 for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) {
358 if (binding->type == QV4::CompiledData::Binding::Type_Object) {
359 const Object *child = compilationUnit->objectAt(index: binding->value.objectIndex);
360 result << enumerateTestCases(compilationUnit, object: child);
361 }
362 }
363
364 return result;
365 }
366};
367
368int quick_test_main(int argc, char **argv, const char *name, const char *sourceDir)
369{
370 return quick_test_main_with_setup(argc, argv, name, sourceDir, setup: nullptr);
371}
372
373int quick_test_main_with_setup(int argc, char **argv, const char *name, const char *sourceDir, QObject *setup)
374{
375 // Peek at arguments to check for '-widgets' argument
376#ifdef QT_QMLTEST_WITH_WIDGETS
377 bool withWidgets = false;
378 for (int index = 1; index < argc; ++index) {
379 if (strcmp(s1: argv[index], s2: "-widgets") == 0) {
380 withWidgets = true;
381 break;
382 }
383 }
384#endif
385
386 QCoreApplication *app = nullptr;
387 if (!QCoreApplication::instance()) {
388#ifdef QT_QMLTEST_WITH_WIDGETS
389 if (withWidgets)
390 app = new QApplication(argc, argv);
391 else
392#endif
393 {
394 app = new QGuiApplication(argc, argv);
395 }
396 }
397
398 if (setup)
399 maybeInvokeSetupMethod(setupObject: setup, member: "applicationAvailable()");
400
401 // Look for QML-specific command-line options.
402 // -import dir Specify an import directory.
403 // -plugins dir Specify a directory where to search for plugins.
404 // -input dir Specify the input directory for test cases.
405 // -translation file Specify the translation file.
406 // -file-selector Specify a file selector
407 QStringList imports;
408 QStringList pluginPaths;
409 QString testPath;
410 QString translationFile;
411 QStringList fileSelectors;
412 int index = 1;
413 QScopedArrayPointer<char *> testArgV(new char *[argc + 1]);
414 testArgV[0] = argv[0];
415 int testArgC = 1;
416 while (index < argc) {
417 if (strcmp(s1: argv[index], s2: "-import") == 0 && (index + 1) < argc) {
418 imports += stripQuotes(s: QString::fromLocal8Bit(str: argv[index + 1]));
419 index += 2;
420 } else if (strcmp(s1: argv[index], s2: "-plugins") == 0 && (index + 1) < argc) {
421 pluginPaths += stripQuotes(s: QString::fromLocal8Bit(str: argv[index + 1]));
422 index += 2;
423 } else if (strcmp(s1: argv[index], s2: "-input") == 0 && (index + 1) < argc) {
424 testPath = stripQuotes(s: QString::fromLocal8Bit(str: argv[index + 1]));
425 index += 2;
426 } else if (strcmp(s1: argv[index], s2: "-opengl") == 0) {
427 ++index;
428#ifdef QT_QMLTEST_WITH_WIDGETS
429 } else if (strcmp(s1: argv[index], s2: "-widgets") == 0) {
430 withWidgets = true;
431 ++index;
432#endif
433 } else if (strcmp(s1: argv[index], s2: "-translation") == 0 && (index + 1) < argc) {
434 translationFile = stripQuotes(s: QString::fromLocal8Bit(str: argv[index + 1]));
435 index += 2;
436 } else if (strcmp(s1: argv[index], s2: "-file-selector") == 0 && (index + 1) < argc) {
437 fileSelectors += stripQuotes(s: QString::fromLocal8Bit(str: argv[index + 1]));
438 index += 2;
439 } else {
440 testArgV[testArgC++] = argv[index++];
441 }
442 }
443 testArgV[testArgC] = 0;
444
445 // Setting currentAppname and currentTestObjectName (via setProgramName) are needed
446 // for the code coverage analysis. Must be done before parseArgs is called.
447 QuickTestResult::setCurrentAppname(argv[0]);
448 QuickTestResult::setProgramName(name);
449
450 QuickTestResult::parseArgs(argc: testArgC, argv: testArgV.data());
451
452#if QT_CONFIG(translation)
453 QTranslator translator;
454 if (!translationFile.isEmpty()) {
455 if (translator.load(filename: translationFile)) {
456 app->installTranslator(messageFile: &translator);
457 } else {
458 qWarning(msg: "Could not load the translation file '%s'.", qPrintable(translationFile));
459 }
460 }
461#endif
462
463#if defined(Q_OS_WINRT)
464 if (testPath.isEmpty())
465 testPath = QLatin1String(":/");
466#endif
467
468 // Determine where to look for the test data.
469 if (testPath.isEmpty() && sourceDir) {
470 const QString s = QString::fromLocal8Bit(str: sourceDir);
471 if (QFile::exists(fileName: s))
472 testPath = s;
473 }
474
475#if defined(Q_OS_ANDROID)
476 if (testPath.isEmpty())
477 testPath = QLatin1String(":/");
478#endif
479
480 if (testPath.isEmpty()) {
481 QDir current = QDir::current();
482#ifdef Q_OS_WIN
483 // Skip release/debug subfolders
484 if (!current.dirName().compare(QLatin1String("Release"), Qt::CaseInsensitive)
485 || !current.dirName().compare(QLatin1String("Debug"), Qt::CaseInsensitive))
486 current.cdUp();
487#endif // Q_OS_WIN
488 testPath = current.absolutePath();
489 }
490 QStringList files;
491
492 const QFileInfo testPathInfo(testPath);
493 if (testPathInfo.isFile()) {
494 if (!testPath.endsWith(s: QLatin1String(".qml"))) {
495 qWarning(msg: "'%s' does not have the suffix '.qml'.", qPrintable(testPath));
496 return 1;
497 }
498 files << testPath;
499 } else if (testPathInfo.isDir()) {
500 // Scan the test data directory recursively, looking for "tst_*.qml" files.
501 const QStringList filters(QStringLiteral("tst_*.qml"));
502 QDirIterator iter(testPathInfo.absoluteFilePath(), filters, QDir::Files,
503 QDirIterator::Subdirectories |
504 QDirIterator::FollowSymlinks);
505 while (iter.hasNext())
506 files += iter.next();
507 files.sort();
508 if (files.isEmpty()) {
509 qWarning(msg: "The directory '%s' does not contain any test files matching '%s'",
510 qPrintable(testPath), qPrintable(filters.front()));
511 return 1;
512 }
513 } else {
514 qWarning(msg: "'%s' does not exist under '%s'.",
515 qPrintable(testPath), qPrintable(QDir::currentPath()));
516 return 1;
517 }
518
519 qputenv(varName: "QT_QTESTLIB_RUNNING", value: "1");
520
521 // Register the custom factory function
522 qmlRegisterSingletonType<QTestRootObject>(uri: "Qt.test.qtestroot", versionMajor: 1, versionMinor: 0, typeName: "QTestRootObject", callback: testRootObject);
523
524 QSet<QString> commandLineTestFunctions(QTest::testFunctions.cbegin(), QTest::testFunctions.cend());
525 const bool filteringTestFunctions = !commandLineTestFunctions.isEmpty();
526
527 // Scan through all of the "tst_*.qml" files and run each of them
528 // in turn with a separate QQuickView (for test isolation).
529 for (const QString &file : qAsConst(t&: files)) {
530 const QFileInfo fi(file);
531 if (!fi.exists())
532 continue;
533
534 QQmlEngine engine;
535 for (const QString &path : qAsConst(t&: imports))
536 engine.addImportPath(dir: path);
537 for (const QString &path : qAsConst(t&: pluginPaths))
538 engine.addPluginPath(dir: path);
539
540 if (!fileSelectors.isEmpty()) {
541 QQmlFileSelector* const qmlFileSelector = new QQmlFileSelector(&engine, &engine);
542 qmlFileSelector->setExtraSelectors(fileSelectors);
543 }
544
545 // Do this down here so that import paths, plugin paths, file selectors, etc. are available
546 // in case the user needs access to them. Do it _before_ the TestCaseCollector parses the
547 // QML files though, because it attempts to import modules, which might not be available
548 // if qmlRegisterType()/QQmlEngine::addImportPath() are called in qmlEngineAvailable().
549 if (setup)
550 maybeInvokeSetupMethod(setupObject: setup, member: "qmlEngineAvailable(QQmlEngine*)", Q_ARG(QQmlEngine*, &engine));
551
552 TestCaseCollector testCaseCollector(fi, &engine);
553 if (!testCaseCollector.errors().isEmpty()) {
554 for (const QQmlError &error : testCaseCollector.errors())
555 qWarning() << error;
556 exit(status: 1);
557 }
558
559 TestCaseCollector::TestCaseList availableTestFunctions = testCaseCollector.testCases();
560 if (QTest::printAvailableFunctions) {
561 for (const QString &function : availableTestFunctions)
562 qDebug(msg: "%s()", qPrintable(function));
563 continue;
564 }
565
566 const QSet<QString> availableTestSet(availableTestFunctions.cbegin(), availableTestFunctions.cend());
567 if (filteringTestFunctions && !availableTestSet.intersects(other: commandLineTestFunctions))
568 continue;
569 commandLineTestFunctions.subtract(other: availableTestSet);
570
571 QQuickView view(&engine, nullptr);
572 view.setFlags(Qt::Window | Qt::WindowSystemMenuHint
573 | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint
574 | Qt::WindowCloseButtonHint);
575 QEventLoop eventLoop;
576 QObject::connect(sender: view.engine(), SIGNAL(quit()),
577 receiver: QTestRootObject::instance(), SLOT(quit()));
578 QObject::connect(sender: view.engine(), SIGNAL(quit()),
579 receiver: &eventLoop, SLOT(quit()));
580 view.rootContext()->setContextProperty
581 (QLatin1String("qtest"), QTestRootObject::instance()); // Deprecated. Use QTestRootObject from Qt.test.qtestroot instead
582
583 view.setObjectName(fi.baseName());
584 view.setTitle(view.objectName());
585 QTestRootObject::instance()->init();
586 QString path = fi.absoluteFilePath();
587 if (path.startsWith(s: QLatin1String(":/")))
588 view.setSource(QUrl(QLatin1String("qrc:") + path.midRef(position: 1)));
589 else
590 view.setSource(QUrl::fromLocalFile(localfile: path));
591
592 while (view.status() == QQuickView::Loading)
593 QTest::qWait(ms: 10);
594 if (view.status() == QQuickView::Error) {
595 handleCompileErrors(fi, view: &view);
596 continue;
597 }
598 if (!QTestRootObject::instance()->hasQuit) {
599 // If the test already quit, then it was performed
600 // synchronously during setSource(). Otherwise it is
601 // an asynchronous test and we need to show the window
602 // and wait for the first frame to be rendered
603 // and then wait for quit indication.
604 view.setFramePosition(QPoint(50, 50));
605 if (view.size().isEmpty()) { // Avoid hangs with empty windows.
606 view.resize(w: 200, h: 200);
607 }
608 view.show();
609 if (!QTest::qWaitForWindowExposed(window: &view)) {
610 qWarning().nospace()
611 << "Test '" << QDir::toNativeSeparators(pathName: path) << "' window not exposed after show().";
612 }
613 view.requestActivate();
614 if (!QTest::qWaitForWindowActive(window: &view)) {
615 qWarning().nospace()
616 << "Test '" << QDir::toNativeSeparators(pathName: path) << "' window not active after requestActivate().";
617 }
618 if (view.isExposed()) {
619 // Defer property update until event loop has started
620 QTimer::singleShot(interval: 0, slot: []() {
621 QTestRootObject::instance()->setWindowShown(true);
622 });
623 } else {
624 qWarning().nospace()
625 << "Test '" << QDir::toNativeSeparators(pathName: path) << "' window was never exposed! "
626 << "If the test case was expecting windowShown, it will hang.";
627 }
628 if (!QTestRootObject::instance()->hasQuit && QTestRootObject::instance()->hasTestCase())
629 eventLoop.exec();
630 }
631 }
632
633 if (setup)
634 maybeInvokeSetupMethod(setupObject: setup, member: "cleanupTestCase()");
635
636 // Flush the current logging stream.
637 QuickTestResult::setProgramName(nullptr);
638 delete app;
639
640 // Check that all test functions passed on the command line were found
641 if (!commandLineTestFunctions.isEmpty()) {
642 qWarning() << "Could not find the following test functions:";
643 for (const QString &functionName : qAsConst(t&: commandLineTestFunctions))
644 qWarning(msg: " %s()", qUtf8Printable(functionName));
645 return commandLineTestFunctions.count();
646 }
647
648 // Return the number of failures as the exit code.
649 return QuickTestResult::exitCode();
650}
651
652QT_END_NAMESPACE
653

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