diff options
Diffstat (limited to 'src/qmltest/quicktest.cpp')
-rw-r--r-- | src/qmltest/quicktest.cpp | 482 |
1 files changed, 280 insertions, 202 deletions
diff --git a/src/qmltest/quicktest.cpp b/src/qmltest/quicktest.cpp index 22d329bb80..1a580f8c69 100644 --- a/src/qmltest/quicktest.cpp +++ b/src/qmltest/quicktest.cpp @@ -1,54 +1,25 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "quicktest.h" +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "quicktest_p.h" #include "quicktestresult_p.h" #include <QtTest/qtestsystem.h> +#include <QtTest/private/qtestcrashhandler_p.h> #include "qtestoptions_p.h" #include <QtQml/qqml.h> #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcontext.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickwindow_p.h> +#include <QtQuick/qquickitem.h> #include <QtQuick/qquickview.h> +#include <QtQuick/qquickwindow.h> #include <QtQml/qjsvalue.h> #include <QtQml/qjsengine.h> #include <QtQml/qqmlpropertymap.h> -#include <QtGui/qopengl.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/qquickitem.h> +#include <qopengl.h> #include <QtCore/qurl.h> #include <QtCore/qfileinfo.h> #include <QtCore/qdir.h> @@ -61,84 +32,155 @@ #include <QtGui/qtextdocument.h> #include <stdio.h> #include <QtGui/QGuiApplication> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/qpa/qplatformintegration.h> #include <QtCore/QTranslator> #include <QtTest/QSignalSpy> #include <QtQml/QQmlFileSelector> #include <private/qqmlcomponent_p.h> - -#ifdef QT_QMLTEST_WITH_WIDGETS -#include <QtWidgets/QApplication> -#endif +#include <private/qv4resolvedtypereference_p.h> QT_BEGIN_NAMESPACE -class QTestRootObject : public QObject +/*! + \since 5.13 + + Returns \c true if \l {QQuickItem::}{updatePolish()} has not been called + on \a item since the last call to \l {QQuickItem::}{polish()}, + otherwise returns \c false. + + When assigning values to properties in QML, any layouting the item + must do as a result of the assignment might not take effect immediately, + but can instead be postponed until the item is polished. For these cases, + you can use this function to ensure that the item has been polished + before the execution of the test continues. For example: + + \code + QVERIFY(QQuickTest::qIsPolishScheduled(item)); + QVERIFY(QQuickTest::qWaitForItemPolished(item)); + \endcode + + Without the call to \c qIsPolishScheduled() above, the + call to \c qWaitForItemPolished() might see that no polish + was scheduled and therefore pass instantly, assuming that + the item had already been polished. This function + makes it obvious why an item wasn't polished and allows tests to + fail early under such circumstances. + + The QML equivalent of this function is + \l {TestCase::}{isPolishScheduled()}. + + \sa QQuickItem::polish(), QQuickItem::updatePolish() +*/ +bool QQuickTest::qIsPolishScheduled(const QQuickItem *item) { - Q_OBJECT - Q_PROPERTY(bool windowShown READ windowShown NOTIFY windowShownChanged) - Q_PROPERTY(bool hasTestCase READ hasTestCase WRITE setHasTestCase NOTIFY hasTestCaseChanged) - Q_PROPERTY(QObject *defined READ defined) -public: - QTestRootObject(QObject *parent = nullptr) - : QObject(parent), hasQuit(false), m_windowShown(false), m_hasTestCase(false) { - m_defined = new QQmlPropertyMap(this); -#if defined(QT_OPENGL_ES_2_ANGLE) - m_defined->insert(QLatin1String("QT_OPENGL_ES_2_ANGLE"), QVariant(true)); + return QQuickItemPrivate::get(item)->polishScheduled; +} + +/*! + \since 6.4 + \overload qIsPolishScheduled() + + Returns \c true if there are any items managed by this window for + which \c qIsPolishScheduled(item) returns \c true, otherwise + returns \c false. + + For example, if an item somewhere within the scene may or may not + be polished, but you need to wait for it if it is, you can use + the following code: + + \code + if (QQuickTest::qIsPolishScheduled(window)) + QVERIFY(QQuickTest::qWaitForPolish(window)); + \endcode + + The QML equivalent of this function is + \l [QML]{TestCase::}{isPolishScheduled()}. + + \sa QQuickItem::polish(), QQuickItem::updatePolish(), + QQuickTest::qWaitForPolish() +*/ +bool QQuickTest::qIsPolishScheduled(const QQuickWindow *window) +{ + return !QQuickWindowPrivate::get(window)->itemsToPolish.isEmpty(); +} + +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +#if QT_DEPRECATED_SINCE(6, 4) +/*! + \since 5.13 + \deprecated [6.4] Use \l qWaitForPolish() instead. + + Waits for \a timeout milliseconds or until + \l {QQuickItem::}{updatePolish()} has been called on \a item. + + Returns \c true if \c updatePolish() was called on \a item within + \a timeout milliseconds, otherwise returns \c false. + + The QML equivalent of this function is + \l {TestCase::}{waitForItemPolished()}. + + \sa QQuickItem::polish(), QQuickItem::updatePolish(), + QQuickTest::qIsPolishScheduled() +*/ +bool QQuickTest::qWaitForItemPolished(const QQuickItem *item, int timeout) +{ + return qWaitForPolish(item, timeout); +} +#endif #endif - } - static QTestRootObject *instance() { - static QPointer<QTestRootObject> object = new QTestRootObject; - if (!object) { - // QTestRootObject was deleted when previous test ended, create a new one - object = new QTestRootObject; - } - return object; - } +/*! + \since 6.4 - bool hasQuit:1; - bool hasTestCase() const { return m_hasTestCase; } - void setHasTestCase(bool value) { m_hasTestCase = value; emit hasTestCaseChanged(); } + Waits for \a timeout milliseconds or until + \l {QQuickItem::}{updatePolish()} has been called on \a item. - bool windowShown() const { return m_windowShown; } - void setWindowShown(bool value) { m_windowShown = value; emit windowShownChanged(); } - QQmlPropertyMap *defined() const { return m_defined; } + Returns \c true if \c updatePolish() was called on \a item within + \a timeout milliseconds, otherwise returns \c false. - void init() { setWindowShown(false); setHasTestCase(false); hasQuit = false; } + \sa QQuickItem::polish(), QQuickItem::updatePolish(), + QQuickTest::qIsPolishScheduled() +*/ +bool QQuickTest::qWaitForPolish(const QQuickItem *item, int timeout) +{ + return QTest::qWaitFor([&]() { return !QQuickItemPrivate::get(item)->polishScheduled; }, timeout); +} -Q_SIGNALS: - void windowShownChanged(); - void hasTestCaseChanged(); +/*! + \since 6.4 -private Q_SLOTS: - void quit() { hasQuit = true; } + Waits for \a timeout milliseconds or until \c qIsPolishScheduled(item) + returns \c false for all items managed by \a window. -private: - bool m_windowShown : 1; - bool m_hasTestCase :1; - QQmlPropertyMap *m_defined; -}; + Returns \c true if \c qIsPolishScheduled(item) returns false for all items + within \a timeout milliseconds, otherwise returns \c false. + + The QML equivalent of this function is + \l [QML]{TestCase::}{waitForPolish()}. -static QObject *testRootObject(QQmlEngine *engine, QJSEngine *jsEngine) + \sa QQuickItem::polish(), QQuickItem::updatePolish(), + QQuickTest::qIsPolishScheduled() +*/ +bool QQuickTest::qWaitForPolish(const QQuickWindow *window, int timeout) { - Q_UNUSED(engine); - Q_UNUSED(jsEngine); - return QTestRootObject::instance(); + return QTest::qWaitFor([&]() { return QQuickWindowPrivate::get(window)->itemsToPolish.isEmpty(); }, timeout); } static inline QString stripQuotes(const QString &s) { - if (s.length() >= 2 && s.startsWith(QLatin1Char('"')) && s.endsWith(QLatin1Char('"'))) - return s.mid(1, s.length() - 2); + if (s.size() >= 2 && s.startsWith(QLatin1Char('"')) && s.endsWith(QLatin1Char('"'))) + return s.mid(1, s.size() - 2); else return s; } -void handleCompileErrors(const QFileInfo &fi, QQuickView *view) +static void handleCompileErrors( + const QFileInfo &fi, const QList<QQmlError> &errors, QQmlEngine *engine, + QQuickView *view = nullptr) { // Error compiling the test - flag failure in the log and continue. - const QList<QQmlError> errors = view->errors(); QuickTestResult results; results.setTestCaseName(fi.baseName()); results.startLogging(); @@ -160,8 +202,11 @@ void handleCompileErrors(const QFileInfo &fi, QQuickView *view) str << ": " << e.description() << '\n'; } str << " Working directory: " << QDir::toNativeSeparators(QDir::current().absolutePath()) << '\n'; - if (QQmlEngine *engine = view->engine()) { - str << " View: " << view->metaObject()->className() << ", import paths:\n"; + if (engine) { + str << " "; + if (view) + str << "View: " << view->metaObject()->className() << ", "; + str << "Import paths:\n"; const auto importPaths = engine->importPathList(); for (const QString &i : importPaths) str << " '" << QDir::toNativeSeparators(i) << "'\n"; @@ -181,25 +226,49 @@ void handleCompileErrors(const QFileInfo &fi, QQuickView *view) results.stopLogging(); } -bool qWaitForSignal(QObject *obj, const char* signal, int timeout = 5000) +class SimpleReceiver : public QObject { + Q_OBJECT +public: + bool signalReceived = false; +public slots: + void slotFun() { signalReceived = true; } +}; + +bool qWaitForSignal(QObject *obj, const char* signal, int timeout) { - QSignalSpy spy(obj, signal); - QElapsedTimer timer; - timer.start(); - - while (!spy.size()) { - int remaining = timeout - int(timer.elapsed()); - if (remaining <= 0) - break; - QCoreApplication::processEvents(QEventLoop::AllEvents, remaining); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QTest::qSleep(10); + if (!obj || !signal) { + qWarning("qWaitForSignal: invalid arguments"); + return false; + } + if (((signal[0] - '0') & 0x03) != QSIGNAL_CODE) { + qWarning("qWaitForSignal: not a valid signal, use the SIGNAL macro"); + return false; } - return spy.size(); + int sig = obj->metaObject()->indexOfSignal(signal + 1); + if (sig == -1) { + const QByteArray ba = QMetaObject::normalizedSignature(signal + 1); + sig = obj->metaObject()->indexOfSignal(ba.constData()); + if (sig == -1) { + qWarning("qWaitForSignal: no such signal %s::%s", obj->metaObject()->className(), + signal); + return false; + } + } + + SimpleReceiver receiver; + static int slot = receiver.metaObject()->indexOfSlot("slotFun()"); + if (!QMetaObject::connect(obj, sig, &receiver, slot)) { + qWarning("qWaitForSignal: failed to connect to signal %s::%s", + obj->metaObject()->className(), signal); + return false; + } + + return QTest::qWaitFor([&]() { return receiver.signalReceived; }, timeout); } -void maybeInvokeSetupMethod(QObject *setupObject, const char *member, QGenericArgument val0 = QGenericArgument(nullptr)) +template <typename... Args> +void maybeInvokeSetupMethod(QObject *setupObject, const char *member, Args &&... args) { // It's OK if it doesn't exist: since we have more than one callback that // can be called, it makes sense if the user only implements one of them. @@ -210,7 +279,7 @@ void maybeInvokeSetupMethod(QObject *setupObject, const char *member, QGenericAr const int methodIndex = setupMetaObject->indexOfMethod(member); if (methodIndex != -1) { const QMetaMethod method = setupMetaObject->method(methodIndex); - method.invoke(setupObject, val0); + method.invoke(setupObject, std::forward<Args>(args)...); } } @@ -221,7 +290,7 @@ class TestCaseCollector public: typedef QList<QString> TestCaseList; - TestCaseCollector(const QFileInfo &fileInfo, QQmlEngine *engine) + TestCaseCollector(const QFileInfo &fileInfo, QQmlEngine *engine) : m_engine(engine) { QString path = fileInfo.absoluteFilePath(); if (path.startsWith(QLatin1String(":/"))) @@ -231,8 +300,10 @@ public: m_errors += component.errors(); if (component.isReady()) { - QQmlRefPointer<CompilationUnit> rootCompilationUnit = QQmlComponentPrivate::get(&component)->compilationUnit; - TestCaseEnumerationResult result = enumerateTestCases(rootCompilationUnit.data()); + QQmlRefPointer<QV4::ExecutableCompilationUnit> rootCompilationUnit + = QQmlComponentPrivate::get(&component)->compilationUnit; + TestCaseEnumerationResult result = enumerateTestCases( + rootCompilationUnit->baseCompilationUnit().data()); m_testCases = result.testCases + result.finalizedPartialTestCases(); m_errors += result.errors; } @@ -244,6 +315,7 @@ public: private: TestCaseList m_testCases; QList<QQmlError> m_errors; + QQmlEngine *m_engine = nullptr; struct TestCaseEnumerationResult { @@ -271,12 +343,14 @@ private: } }; - TestCaseEnumerationResult enumerateTestCases(CompilationUnit *compilationUnit, const Object *object = nullptr) + TestCaseEnumerationResult enumerateTestCases( + const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, + const QV4::CompiledData::Object *object = nullptr) { QQmlType testCaseType; for (quint32 i = 0, count = compilationUnit->importCount(); i < count; ++i) { const Import *import = compilationUnit->importAt(i); - if (compilationUnit->stringAt(import->uriIndex) != QLatin1Literal("QtTest")) + if (compilationUnit->stringAt(import->uriIndex) != QLatin1String("QtTest")) continue; QString testCaseTypeName(QStringLiteral("TestCase")); @@ -284,7 +358,8 @@ private: if (!typeQualifier.isEmpty()) testCaseTypeName = typeQualifier % QLatin1Char('.') % testCaseTypeName; - testCaseType = compilationUnit->typeNameCache->query(testCaseTypeName).type; + testCaseType = compilationUnit->typeNameCache->query( + testCaseTypeName, QQmlTypeLoader::get(m_engine)).type; if (testCaseType.isValid()) break; } @@ -293,25 +368,29 @@ private: if (!object) // Start at root of compilation unit if not enumerating a specific child object = compilationUnit->objectAt(0); + if (object->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot)) + return result; - if (CompilationUnit *superTypeUnit = compilationUnit->resolvedTypes.value(object->inheritedTypeNameIndex)->compilationUnit.data()) { + if (const auto superTypeUnit = compilationUnit->resolvedType(object->inheritedTypeNameIndex) + ->compilationUnit()) { // We have a non-C++ super type, which could indicate we're a subtype of a TestCase if (testCaseType.isValid() && superTypeUnit->url() == testCaseType.sourceUrl()) result.isTestCase = true; - else + else if (superTypeUnit->url() != compilationUnit->url()) { // urls are the same for inline component, avoid infinite recursion result = enumerateTestCases(superTypeUnit); + } if (result.isTestCase) { // Look for override of name in this type for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) { - if (compilationUnit->stringAt(binding->propertyNameIndex) == QLatin1Literal("name")) { - if (binding->type == QV4::CompiledData::Binding::Type_String) { + if (compilationUnit->stringAt(binding->propertyNameIndex) == QLatin1String("name")) { + if (binding->type() == QV4::CompiledData::Binding::Type_String) { result.testCaseName = compilationUnit->stringAt(binding->stringIndex); } else { QQmlError error; error.setUrl(compilationUnit->url()); - error.setLine(binding->location.line); - error.setColumn(binding->location.column); + error.setLine(binding->location.line()); + error.setColumn(binding->location.column()); error.setDescription(QStringLiteral("the 'name' property of a TestCase must be a literal string")); result.errors << error; } @@ -323,10 +402,10 @@ private: auto functionsEnd = compilationUnit->objectFunctionsEnd(object); for (auto function = compilationUnit->objectFunctionsBegin(object); function != functionsEnd; ++function) { QString functionName = compilationUnit->stringAt(function->nameIndex); - if (!(functionName.startsWith(QLatin1Literal("test_")) || functionName.startsWith(QLatin1Literal("benchmark_")))) + if (!(functionName.startsWith(QLatin1String("test_")) || functionName.startsWith(QLatin1String("benchmark_")))) continue; - if (functionName.endsWith(QLatin1Literal("_data"))) + if (functionName.endsWith(QLatin1String("_data"))) continue; result.testFunctions << functionName; @@ -335,8 +414,8 @@ private: } for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) { - if (binding->type == QV4::CompiledData::Binding::Type_Object) { - const Object *child = compilationUnit->objectAt(binding->value.objectIndex); + if (binding->type() == QV4::CompiledData::Binding::Type_Object) { + const QV4::CompiledData::Object *child = compilationUnit->objectAt(binding->value.objectIndex); result << enumerateTestCases(compilationUnit, child); } } @@ -352,28 +431,9 @@ int quick_test_main(int argc, char **argv, const char *name, const char *sourceD int quick_test_main_with_setup(int argc, char **argv, const char *name, const char *sourceDir, QObject *setup) { - // Peek at arguments to check for '-widgets' argument -#ifdef QT_QMLTEST_WITH_WIDGETS - bool withWidgets = false; - for (int index = 1; index < argc; ++index) { - if (strcmp(argv[index], "-widgets") == 0) { - withWidgets = true; - break; - } - } -#endif - - QCoreApplication *app = nullptr; - if (!QCoreApplication::instance()) { -#ifdef QT_QMLTEST_WITH_WIDGETS - if (withWidgets) - app = new QApplication(argc, argv); - else -#endif - { - app = new QGuiApplication(argc, argv); - } - } + QScopedPointer<QCoreApplication> app; + if (!QCoreApplication::instance()) + app.reset(new QGuiApplication(argc, argv)); if (setup) maybeInvokeSetupMethod(setup, "applicationAvailable()"); @@ -405,11 +465,6 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch index += 2; } else if (strcmp(argv[index], "-opengl") == 0) { ++index; -#ifdef QT_QMLTEST_WITH_WIDGETS - } else if (strcmp(argv[index], "-widgets") == 0) { - withWidgets = true; - ++index; -#endif } else if (strcmp(argv[index], "-translation") == 0 && (index + 1) < argc) { translationFile = stripQuotes(QString::fromLocal8Bit(argv[index + 1])); index += 2; @@ -440,17 +495,18 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch } #endif -#if defined(Q_OS_ANDROID) || defined(Q_OS_WINRT) - if (testPath.isEmpty()) - testPath = QLatin1String(":/"); -#endif - // Determine where to look for the test data. if (testPath.isEmpty() && sourceDir) { const QString s = QString::fromLocal8Bit(sourceDir); if (QFile::exists(s)) testPath = s; } + +#if defined(Q_OS_ANDROID) || defined(Q_OS_INTEGRITY) + if (testPath.isEmpty()) + testPath = QLatin1String(":/"); +#endif + if (testPath.isEmpty()) { QDir current = QDir::current(); #ifdef Q_OS_WIN @@ -465,11 +521,35 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch const QFileInfo testPathInfo(testPath); if (testPathInfo.isFile()) { - if (!testPath.endsWith(QLatin1String(".qml"))) { - qWarning("'%s' does not have the suffix '.qml'.", qPrintable(testPath)); + if (testPath.endsWith(QLatin1String(".qml"))) { + files << testPath; + } else if (testPath.endsWith(QLatin1String(".qmltests"))) { + QFile file(testPath); + if (file.open(QIODevice::ReadOnly)) { + while (!file.atEnd()) { + const QString filePath = testPathInfo.dir() + .filePath(QString::fromUtf8(file.readLine())) + .trimmed(); + const QFileInfo f(filePath); + if (f.exists()) + files.append(filePath); + else + qWarning("The test file '%s' does not exists", qPrintable(filePath)); + } + file.close(); + files.sort(); + if (files.isEmpty()) { + qWarning("The file '%s' does not contain any tests files", + qPrintable(testPath)); + return 1; + } + } else { + qWarning("Could not read '%s'", qPrintable(testPath)); + } + } else { + qWarning("'%s' does not have the suffix '.qml' or '.qmltests'.", qPrintable(testPath)); return 1; } - files << testPath; } else if (testPathInfo.isDir()) { // Scan the test data directory recursively, looking for "tst_*.qml" files. const QStringList filters(QStringLiteral("tst_*.qml")); @@ -490,25 +570,27 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch return 1; } - qputenv("QT_QTESTLIB_RUNNING", "1"); + std::optional<QTest::CrashHandler::FatalSignalHandler> handler; + QTest::CrashHandler::prepareStackTrace(); + if (!QTest::Internal::noCrashHandler) + handler.emplace(); - // Register the test object - qmlRegisterSingletonType<QTestRootObject>("Qt.test.qtestroot", 1, 0, "QTestRootObject", testRootObject); + qputenv("QT_QTESTLIB_RUNNING", "1"); - QSet<QString> commandLineTestFunctions = QTest::testFunctions.toSet(); + QSet<QString> commandLineTestFunctions(QTest::testFunctions.cbegin(), QTest::testFunctions.cend()); const bool filteringTestFunctions = !commandLineTestFunctions.isEmpty(); // Scan through all of the "tst_*.qml" files and run each of them // in turn with a separate QQuickView (for test isolation). - for (const QString &file : qAsConst(files)) { + for (const QString &file : std::as_const(files)) { const QFileInfo fi(file); if (!fi.exists()) continue; QQmlEngine engine; - for (const QString &path : qAsConst(imports)) + for (const QString &path : std::as_const(imports)) engine.addImportPath(path); - for (const QString &path : qAsConst(pluginPaths)) + for (const QString &path : std::as_const(pluginPaths)) engine.addPluginPath(path); if (!fileSelectors.isEmpty()) { @@ -525,9 +607,8 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch TestCaseCollector testCaseCollector(fi, &engine); if (!testCaseCollector.errors().isEmpty()) { - for (const QQmlError &error : testCaseCollector.errors()) - qWarning() << error; - exit(1); + handleCompileErrors(fi, testCaseCollector.errors(), &engine); + continue; } TestCaseCollector::TestCaseList availableTestFunctions = testCaseCollector.testCases(); @@ -537,7 +618,7 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch continue; } - const QSet<QString> availableTestSet = availableTestFunctions.toSet(); + const QSet<QString> availableTestSet(availableTestFunctions.cbegin(), availableTestFunctions.cend()); if (filteringTestFunctions && !availableTestSet.intersects(commandLineTestFunctions)) continue; commandLineTestFunctions.subtract(availableTestSet); @@ -552,56 +633,52 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch QObject::connect(view.engine(), SIGNAL(quit()), &eventLoop, SLOT(quit())); view.rootContext()->setContextProperty - (QLatin1String("qtest"), QTestRootObject::instance()); // Deprecated. Use QTestRootObject from Qt.test.qtestroot instead + (QLatin1String("qtest"), QTestRootObject::instance()); // Deprecated. Use QTestRootObject from QtTest instead view.setObjectName(fi.baseName()); view.setTitle(view.objectName()); QTestRootObject::instance()->init(); QString path = fi.absoluteFilePath(); if (path.startsWith(QLatin1String(":/"))) - view.setSource(QUrl(QLatin1String("qrc:") + path.midRef(1))); + view.setSource(QUrl(QLatin1String("qrc:") + QStringView{path}.mid(1))); else view.setSource(QUrl::fromLocalFile(path)); while (view.status() == QQuickView::Loading) QTest::qWait(10); if (view.status() == QQuickView::Error) { - handleCompileErrors(fi, &view); + handleCompileErrors(fi, view.errors(), view.engine(), &view); continue; } - if (!QTestRootObject::instance()->hasQuit) { - // If the test already quit, then it was performed - // synchronously during setSource(). Otherwise it is - // an asynchronous test and we need to show the window - // and wait for the first frame to be rendered - // and then wait for quit indication. - view.setFramePosition(QPoint(50, 50)); - if (view.size().isEmpty()) { // Avoid hangs with empty windows. - view.resize(200, 200); - } - view.show(); - if (!QTest::qWaitForWindowExposed(&view)) { - qWarning().nospace() - << "Test '" << QDir::toNativeSeparators(path) << "' window not exposed after show()."; - } + + view.setFramePosition(QPoint(50, 50)); + if (view.size().isEmpty()) { // Avoid hangs with empty windows. + view.resize(200, 200); + } + view.show(); + if (!QTest::qWaitForWindowExposed(&view)) { + qWarning().nospace() + << "Test '" << QDir::toNativeSeparators(path) << "' window not exposed after show()."; + } + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation)) { view.requestActivate(); if (!QTest::qWaitForWindowActive(&view)) { qWarning().nospace() << "Test '" << QDir::toNativeSeparators(path) << "' window not active after requestActivate()."; } - if (view.isExposed()) { - // Defer property update until event loop has started - QTimer::singleShot(0, []() { - QTestRootObject::instance()->setWindowShown(true); - }); - } else { - qWarning().nospace() - << "Test '" << QDir::toNativeSeparators(path) << "' window was never exposed! " - << "If the test case was expecting windowShown, it will hang."; - } - if (!QTestRootObject::instance()->hasQuit && QTestRootObject::instance()->hasTestCase()) - eventLoop.exec(); } + if (view.isExposed()) { + // Defer property update until event loop has started + QTimer::singleShot(0, []() { + QTestRootObject::instance()->setWindowShown(true); + }); + } else { + qWarning().nospace() + << "Test '" << QDir::toNativeSeparators(path) << "' window was never exposed! " + << "If the test case was expecting windowShown, it will hang."; + } + if (!QTestRootObject::instance()->hasQuit && QTestRootObject::instance()->hasTestCase()) + eventLoop.exec(); } if (setup) @@ -609,14 +686,14 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch // Flush the current logging stream. QuickTestResult::setProgramName(nullptr); - delete app; + app.reset(); // Check that all test functions passed on the command line were found if (!commandLineTestFunctions.isEmpty()) { qWarning() << "Could not find the following test functions:"; - for (const QString &functionName : qAsConst(commandLineTestFunctions)) + for (const QString &functionName : std::as_const(commandLineTestFunctions)) qWarning(" %s()", qUtf8Printable(functionName)); - return commandLineTestFunctions.count(); + return commandLineTestFunctions.size(); } // Return the number of failures as the exit code. @@ -625,4 +702,5 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch QT_END_NAMESPACE +#include "moc_quicktest_p.cpp" #include "quicktest.moc" |