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

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