aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmltest/quicktest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmltest/quicktest.cpp')
-rw-r--r--src/qmltest/quicktest.cpp303
1 files changed, 186 insertions, 117 deletions
diff --git a/src/qmltest/quicktest.cpp b/src/qmltest/quicktest.cpp
index 276463c7c5..ce29dadb47 100644
--- a/src/qmltest/quicktest.cpp
+++ b/src/qmltest/quicktest.cpp
@@ -1,52 +1,19 @@
-/****************************************************************************
-**
-** 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$
-**
-****************************************************************************/
+// 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>
@@ -110,7 +77,38 @@ bool QQuickTest::qIsPolishScheduled(const QQuickItem *item)
}
/*!
+ \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.
@@ -126,21 +124,61 @@ bool QQuickTest::qIsPolishScheduled(const QQuickItem *item)
*/
bool QQuickTest::qWaitForItemPolished(const QQuickItem *item, int timeout)
{
+ return qWaitForPolish(item, timeout);
+}
+#endif
+#endif
+
+/*!
+ \since 6.4
+
+ 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.
+
+ \sa QQuickItem::polish(), QQuickItem::updatePolish(),
+ QQuickTest::qIsPolishScheduled()
+*/
+bool QQuickTest::qWaitForPolish(const QQuickItem *item, int timeout)
+{
return QTest::qWaitFor([&]() { return !QQuickItemPrivate::get(item)->polishScheduled; }, timeout);
}
+/*!
+ \since 6.4
+
+ Waits for \a timeout milliseconds or until \c qIsPolishScheduled(item)
+ returns \c false for all items managed by \a window.
+
+ 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()}.
+
+ \sa QQuickItem::polish(), QQuickItem::updatePolish(),
+ QQuickTest::qIsPolishScheduled()
+*/
+bool QQuickTest::qWaitForPolish(const QQuickWindow *window, int timeout)
+{
+ 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();
@@ -162,8 +200,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";
@@ -183,25 +224,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;
+ }
+
+ 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 spy.size();
+ 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.
@@ -212,7 +277,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)...);
}
}
@@ -223,7 +288,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(":/")))
@@ -235,7 +300,8 @@ public:
if (component.isReady()) {
QQmlRefPointer<QV4::ExecutableCompilationUnit> rootCompilationUnit
= QQmlComponentPrivate::get(&component)->compilationUnit;
- TestCaseEnumerationResult result = enumerateTestCases(rootCompilationUnit.data());
+ TestCaseEnumerationResult result = enumerateTestCases(
+ rootCompilationUnit->baseCompilationUnit().data());
m_testCases = result.testCases + result.finalizedPartialTestCases();
m_errors += result.errors;
}
@@ -247,6 +313,7 @@ public:
private:
TestCaseList m_testCases;
QList<QQmlError> m_errors;
+ QQmlEngine *m_engine = nullptr;
struct TestCaseEnumerationResult
{
@@ -275,8 +342,8 @@ private:
};
TestCaseEnumerationResult enumerateTestCases(
- const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
- const Object *object = nullptr)
+ const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
+ const QV4::CompiledData::Object *object = nullptr)
{
QQmlType testCaseType;
for (quint32 i = 0, count = compilationUnit->importCount(); i < count; ++i) {
@@ -289,7 +356,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;
}
@@ -298,11 +366,11 @@ private:
if (!object) // Start at root of compilation unit if not enumerating a specific child
object = compilationUnit->objectAt(0);
- if (object->flags & Object::IsInlineComponentRoot)
+ if (object->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot))
return result;
- if (const auto superTypeUnit = compilationUnit->resolvedTypes.value(
- object->inheritedTypeNameIndex)->compilationUnit()) {
+ 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;
@@ -314,13 +382,13 @@ private:
// Look for override of name in this type
for (auto binding = object->bindingsBegin(); binding != object->bindingsEnd(); ++binding) {
if (compilationUnit->stringAt(binding->propertyNameIndex) == QLatin1String("name")) {
- if (binding->type == QV4::CompiledData::Binding::Type_String) {
+ 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;
}
@@ -344,8 +412,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);
}
}
@@ -361,9 +429,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)
{
- QCoreApplication *app = nullptr;
+ QScopedPointer<QCoreApplication> app;
if (!QCoreApplication::instance())
- app = new QGuiApplication(argc, argv);
+ app.reset(new QGuiApplication(argc, argv));
if (setup)
maybeInvokeSetupMethod(setup, "applicationAvailable()");
@@ -432,7 +500,7 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch
testPath = s;
}
-#if defined(Q_OS_ANDROID)
+#if defined(Q_OS_ANDROID) || defined(Q_OS_INTEGRITY)
if (testPath.isEmpty())
testPath = QLatin1String(":/");
#endif
@@ -500,6 +568,11 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch
return 1;
}
+ std::optional<QTest::CrashHandler::FatalSignalHandler> handler;
+ QTest::CrashHandler::prepareStackTrace();
+ if (!QTest::Internal::noCrashHandler)
+ handler.emplace();
+
qputenv("QT_QTESTLIB_RUNNING", "1");
QSet<QString> commandLineTestFunctions(QTest::testFunctions.cbegin(), QTest::testFunctions.cend());
@@ -507,15 +580,15 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch
// 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()) {
@@ -532,9 +605,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();
@@ -573,42 +645,36 @@ int quick_test_main_with_setup(int argc, char **argv, const char *name, const ch
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.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();
+
+ 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.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 (setup)
@@ -616,14 +682,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.
@@ -631,3 +697,6 @@ 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"