diff options
Diffstat (limited to 'src/testlib')
-rw-r--r-- | src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp | 8 | ||||
-rw-r--r-- | src/testlib/qbenchmarkmetric.cpp | 190 | ||||
-rw-r--r-- | src/testlib/qtest.h | 29 | ||||
-rw-r--r-- | src/testlib/qtest_gui.h | 31 | ||||
-rw-r--r-- | src/testlib/qtest_network.h | 3 | ||||
-rw-r--r-- | src/testlib/qtestblacklist.cpp | 14 | ||||
-rw-r--r-- | src/testlib/qtestcase.cpp | 106 | ||||
-rw-r--r-- | src/testlib/qtestcase.h | 24 | ||||
-rw-r--r-- | src/testlib/qtestcase.qdoc | 49 | ||||
-rw-r--r-- | src/testlib/qtestkeyboard.h | 20 | ||||
-rw-r--r-- | src/testlib/qtestsystem.h | 71 | ||||
-rw-r--r-- | src/testlib/qtestutil_macos.mm | 27 | ||||
-rw-r--r-- | src/testlib/qtestutil_macos_p.h | 11 |
13 files changed, 379 insertions, 204 deletions
diff --git a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp index 01ee8102f4..990b7a38d7 100644 --- a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp +++ b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp @@ -306,5 +306,13 @@ QTest::keyClick(myWindow, Qt::Key_Escape); QTest::keyClick(myWindow, Qt::Key_Escape, Qt::ShiftModifier, 200); //! [29] +//! [30] +MyObject obj; +obj.startup(); +QTest::qWaitFor([&]() { + return obj.isReady(); +}, 3000); +//! [30] + } diff --git a/src/testlib/qbenchmarkmetric.cpp b/src/testlib/qbenchmarkmetric.cpp index 0ba55dbeb7..0f4f915ed3 100644 --- a/src/testlib/qbenchmarkmetric.cpp +++ b/src/testlib/qbenchmarkmetric.cpp @@ -40,6 +40,53 @@ #include <QtTest/private/qbenchmarkmetric_p.h> +QT_BEGIN_NAMESPACE + +namespace QTest { + +struct QBenchmarkMetricKey { + int metric; + const char * name; + const char * unit; +}; + +static const QBenchmarkMetricKey entries[] = { + { FramesPerSecond, "FramesPerSecond", "fps" }, + { BitsPerSecond, "BitsPerSecond", "bits/s" }, + { BytesPerSecond, "BytesPerSecond", "bytes/s" }, + { WalltimeMilliseconds, "WalltimeMilliseconds", "msecs" }, + { CPUTicks, "CPUTicks", "CPU ticks" }, + { InstructionReads, "InstructionReads", "instruction reads" }, + { Events, "Events", "events" }, + { WalltimeNanoseconds, "WalltimeNanoseconds", "nsecs" }, + { BytesAllocated, "BytesAllocated", "bytes" }, + { CPUMigrations, "CPUMigrations", "CPU migrations" }, + { CPUCycles, "CPUCycles", "CPU cycles" }, + { BusCycles, "BusCycles", "bus cycles" }, + { StalledCycles, "StalledCycles", "stalled cycles" }, + { Instructions, "Instructions", "instructions" }, + { BranchInstructions, "BranchInstructions", "branch instructions" }, + { BranchMisses, "BranchMisses", "branch misses" }, + { CacheReferences, "CacheReferences", "cache references" }, + { CacheReads, "CacheReads", "cache loads" }, + { CacheWrites, "CacheWrites", "cache stores" }, + { CachePrefetches, "CachePrefetches", "cache prefetches" }, + { CacheMisses, "CacheMisses", "cache misses" }, + { CacheReadMisses, "CacheReadMisses", "cache load misses" }, + { CacheWriteMisses, "CacheWriteMisses", "cache store misses" }, + { CachePrefetchMisses, "CachePrefetchMisses", "cache prefetch misses" }, + { ContextSwitches, "ContextSwitches", "context switches" }, + { PageFaults, "PageFaults", "page faults" }, + { MinorPageFaults, "MinorPageFaults", "minor page faults" }, + { MajorPageFaults, "MajorPageFaults", "major page faults" }, + { AlignmentFaults, "AlignmentFaults", "alignment faults" }, + { EmulationFaults, "EmulationFaults", "emulation faults" }, + { RefCPUCycles, "RefCPUCycles", "Reference CPU cycles" }, +}; +static const int NumEntries = sizeof(entries) / sizeof(entries[0]); + +} + /*! \enum QTest::QBenchmarkMetric \since 4.7 @@ -92,73 +139,11 @@ */ const char * QTest::benchmarkMetricName(QBenchmarkMetric metric) { - switch (metric) { - case FramesPerSecond: - return "FramesPerSecond"; - case BitsPerSecond: - return "BitsPerSecond"; - case BytesPerSecond: - return "BytesPerSecond"; - case WalltimeMilliseconds: - return "WalltimeMilliseconds"; - case Events: - return "Events"; - case CPUTicks: - return "CPUTicks"; - case CPUMigrations: - return "CPUMigrations"; - case CPUCycles: - return "CPUCycles"; - case RefCPUCycles: - return "RefCPUCycles"; - case BusCycles: - return "BusCycles"; - case StalledCycles: - return "StalledCycles"; - case InstructionReads: - return "InstructionReads"; - case Instructions: - return "Instructions"; - case WalltimeNanoseconds: - return "WalltimeNanoseconds"; - case BytesAllocated: - return "BytesAllocated"; - case BranchInstructions: - return "BranchInstructions"; - case BranchMisses: - return "BranchMisses"; - case CacheReferences: - return "CacheReferences"; - case CacheReads: - return "CacheReads"; - case CacheWrites: - return "CacheWrites"; - case CachePrefetches: - return "CachePrefetches"; - case CacheMisses: - return "CacheMisses"; - case CacheReadMisses: - return "CacheReadMisses"; - case CacheWriteMisses: - return "CacheWriteMisses"; - case CachePrefetchMisses: - return "CachePrefetchMisses"; - case ContextSwitches: - return "ContextSwitches"; - case PageFaults: - return "PageFaults"; - case MinorPageFaults: - return "MinorPageFaults"; - case MajorPageFaults: - return "MajorPageFaults"; - case AlignmentFaults: - return "AlignmentFaults"; - case EmulationFaults: - return "EmulationFaults"; - default: - return ""; - } -}; + if (metric >= 0 && metric < QTest::NumEntries) + return entries[metric].name; + + return ""; +} /*! \since 4.7 @@ -166,71 +151,10 @@ const char * QTest::benchmarkMetricName(QBenchmarkMetric metric) */ const char * QTest::benchmarkMetricUnit(QBenchmarkMetric metric) { - switch (metric) { - case FramesPerSecond: - return "fps"; - case BitsPerSecond: - return "bits/s"; - case BytesPerSecond: - return "bytes/s"; - case WalltimeMilliseconds: - return "msecs"; - case Events: - return "events"; - case CPUTicks: - return "CPU ticks"; - case CPUMigrations: - return "CPU migrations"; - case CPUCycles: - return "CPU cycles"; - case RefCPUCycles: - return "Reference CPU cycles"; - case BusCycles: - return "bus cycles"; - case StalledCycles: - return "stalled cycles"; - case InstructionReads: - return "instruction reads"; - case Instructions: - return "instructions"; - case WalltimeNanoseconds: - return "nsecs"; - case BytesAllocated: - return "bytes"; - case BranchInstructions: - return "branch instructions"; - case BranchMisses: - return "branch misses"; - case CacheReferences: - return "cache references"; - case CacheReads: - return "cache loads"; - case CacheWrites: - return "cache stores"; - case CachePrefetches: - return "cache prefetches"; - case CacheMisses: - return "cache misses"; - case CacheReadMisses: - return "cache load misses"; - case CacheWriteMisses: - return "cache store misses"; - case CachePrefetchMisses: - return "cache prefetch misses"; - case ContextSwitches: - return "context switches"; - case PageFaults: - return "page faults"; - case MinorPageFaults: - return "minor page faults"; - case MajorPageFaults: - return "major page faults"; - case AlignmentFaults: - return "alignment faults"; - case EmulationFaults: - return "emulation faults"; - default: - return ""; - } + if (metric >= 0 && metric < QTest::NumEntries) + return entries[metric].unit; + + return ""; } +QT_END_NAMESPACE diff --git a/src/testlib/qtest.h b/src/testlib/qtest.h index ba63df5f36..ca045120d5 100644 --- a/src/testlib/qtest.h +++ b/src/testlib/qtest.h @@ -65,9 +65,14 @@ QT_BEGIN_NAMESPACE namespace QTest { +template <> inline char *toString(const QStringView &str) +{ + return QTest::toPrettyUnicode(str); +} + template<> inline char *toString(const QString &str) { - return QTest::toPrettyUnicode(reinterpret_cast<const ushort *>(str.constData()), str.length()); + return toString(QStringView(str)); } template<> inline char *toString(const QLatin1String &str) @@ -84,21 +89,21 @@ template<> inline char *toString(const QByteArray &ba) template<> inline char *toString(const QTime &time) { return time.isValid() - ? qstrdup(qPrintable(time.toString(QLatin1String("hh:mm:ss.zzz")))) + ? qstrdup(qPrintable(time.toString(QStringViewLiteral("hh:mm:ss.zzz")))) : qstrdup("Invalid QTime"); } template<> inline char *toString(const QDate &date) { return date.isValid() - ? qstrdup(qPrintable(date.toString(QLatin1String("yyyy/MM/dd")))) + ? qstrdup(qPrintable(date.toString(QStringViewLiteral("yyyy/MM/dd")))) : qstrdup("Invalid QDate"); } template<> inline char *toString(const QDateTime &dateTime) { return dateTime.isValid() - ? qstrdup(qPrintable(dateTime.toString(QLatin1String("yyyy/MM/dd hh:mm:ss.zzz[t]")))) + ? qstrdup(qPrintable(dateTime.toString(QStringViewLiteral("yyyy/MM/dd hh:mm:ss.zzz[t]")))) : qstrdup("Invalid QDateTime"); } #endif // QT_NO_DATESTRING @@ -194,6 +199,22 @@ template<> inline char *toString(const QVariant &v) return qstrdup(vstring.constData()); } +template <typename T1, typename T2> +inline char *toString(const QPair<T1, T2> &pair) +{ + const QScopedArrayPointer<char> first(toString(pair.first)); + const QScopedArrayPointer<char> second(toString(pair.second)); + return toString(QString::asprintf("QPair(%s,%s)", first.data(), second.data())); +} + +template <typename T1, typename T2> +inline char *toString(const std::pair<T1, T2> &pair) +{ + const QScopedArrayPointer<char> first(toString(pair.first)); + const QScopedArrayPointer<char> second(toString(pair.second)); + return toString(QString::asprintf("std::pair(%s,%s)", first.data(), second.data())); +} + inline char *toString(std::nullptr_t) { return toString(QLatin1String("nullptr")); diff --git a/src/testlib/qtest_gui.h b/src/testlib/qtest_gui.h index d0d56e7bd0..d4b2de2214 100644 --- a/src/testlib/qtest_gui.h +++ b/src/testlib/qtest_gui.h @@ -59,6 +59,9 @@ #include <QtGui/qpixmap.h> #include <QtGui/qimage.h> #include <QtGui/qregion.h> +#include <QtGui/qvector2d.h> +#include <QtGui/qvector3d.h> +#include <QtGui/qvector4d.h> #ifdef QT_WIDGETS_LIB #include <QtGui/qicon.h> @@ -80,7 +83,7 @@ namespace QTest */ template<> inline char *toString(const QColor &color) { - return qstrdup(color.name().toLocal8Bit().constData()); + return qstrdup(color.name(QColor::HexArgb).toLocal8Bit().constData()); } template<> inline char *toString(const QRegion ®ion) @@ -116,6 +119,32 @@ template<> inline char *toString(const QRegion ®ion) return qstrdup(result.constData()); } +#ifndef QT_NO_VECTOR2D +template<> inline char *toString(const QVector2D &v) +{ + QByteArray result = "QVector2D(" + QByteArray::number(double(v.x())) + ", " + + QByteArray::number(double(v.y())) + ')'; + return qstrdup(result.constData()); +} +#endif // !QT_NO_VECTOR2D +#ifndef QT_NO_VECTOR3D +template<> inline char *toString(const QVector3D &v) +{ + QByteArray result = "QVector3D(" + QByteArray::number(double(v.x())) + ", " + + QByteArray::number(double(v.y())) + ", " + QByteArray::number(double(v.z())) + ')'; + return qstrdup(result.constData()); +} +#endif // !QT_NO_VECTOR3D +#ifndef QT_NO_VECTOR4D +template<> inline char *toString(const QVector4D &v) +{ + QByteArray result = "QVector4D(" + QByteArray::number(double(v.x())) + ", " + + QByteArray::number(double(v.y())) + ", " + QByteArray::number(double(v.z())) + + ", " + QByteArray::number(double(v.w())) + ')'; + return qstrdup(result.constData()); +} +#endif // !QT_NO_VECTOR4D + inline bool qCompare(QIcon const &t1, QIcon const &t2, const char *actual, const char *expected, const char *file, int line) { diff --git a/src/testlib/qtest_network.h b/src/testlib/qtest_network.h index 6f6b4c1b8e..57a37734fc 100644 --- a/src/testlib/qtest_network.h +++ b/src/testlib/qtest_network.h @@ -67,7 +67,8 @@ namespace QTest /*! \internal */ -inline char *toString(const QHostAddress &addr) +template<> +inline char *toString<QHostAddress>(const QHostAddress &addr) { switch (addr.protocol()) { case QAbstractSocket::UnknownNetworkLayerProtocol: diff --git a/src/testlib/qtestblacklist.cpp b/src/testlib/qtestblacklist.cpp index 57661bb389..b24ce0578e 100644 --- a/src/testlib/qtestblacklist.cpp +++ b/src/testlib/qtestblacklist.cpp @@ -58,9 +58,10 @@ QT_BEGIN_NAMESPACE referring to this documentation is kind to readers. Comments can also be used to indicate the reasons for ignoring particular cases. - A key names a platform, O/S, distribution, tool-chain or architecture; a ! - prefix reverses what it checks. A version, joined to a key (at present, only - for distributions and for msvc) with a hyphen, limits the key to the specific + The key "ci" applies only when run by COIN. Other keys name platforms, + operating systems, distributions, tool-chains or architectures; a ! prefix + reverses what it checks. A version, joined to a key (at present, only for + distributions and for msvc) with a hyphen, limits the key to the specific version. A keyword line matches if every key on it applies to the present run. Successive lines are alternate conditions for ignoring a test. @@ -70,13 +71,18 @@ QT_BEGIN_NAMESPACE Subsequent lines give conditions for ignoring this test. # See qtbase/src/testlib/qtestblacklist.cpp for format - osx + # Test doesn't work on QNX at all + qnx # QTBUG-12345 [testFunction] linux windows 64bit + # Flaky in COIN on macOS, not reproducible by developers + [testSlowly] + ci osx + # Needs basic C++11 support [testfunction2:testData] msvc-2010 diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp index 4c5c9e1eb8..3b5acb6b95 100644 --- a/src/testlib/qtestcase.cpp +++ b/src/testlib/qtestcase.cpp @@ -275,6 +275,11 @@ namespace QTest static QObject *currentTestObject = 0; static QString mainSourcePath; +#if defined(Q_OS_MACOS) + bool macNeedsActivate = false; + IOPMAssertionID powerID; +#endif + class TestMethods { Q_DISABLE_COPY(TestMethods) public: @@ -1269,6 +1274,16 @@ char *toPrettyCString(const char *p, int length) return buffer.take(); } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +// this used to be the signature up to and including Qt 5.9 +// keep it for BC reasons: +Q_TESTLIB_EXPORT +char *toPrettyUnicode(const ushort *p, int length) +{ + return toPrettyUnicode(QStringView(p, length)); +} +#endif + /*! \internal Returns the same QString but with only the ASCII characters still shown; @@ -1276,8 +1291,10 @@ char *toPrettyCString(const char *p, int length) Similar to QDebug::putString(). */ -char *toPrettyUnicode(const ushort *p, int length) +char *toPrettyUnicode(QStringView string) { + auto p = reinterpret_cast<const ushort *>(string.utf16()); + auto length = string.size(); // keep it simple for the vast majority of cases bool trimmed = false; QScopedArrayPointer<char> buffer(new char[256]); @@ -1343,9 +1360,7 @@ void TestMethods::invokeTests(QObject *testObject) const { const QMetaObject *metaObject = testObject->metaObject(); QTEST_ASSERT(metaObject); - QTestLog::startLogging(); QTestResult::setCurrentTestFunction("initTestCase"); - QTestTable::globalTestTable(); if (m_initTestCaseDataMethod.isValid()) m_initTestCaseDataMethod.invoke(testObject, Qt::DirectConnection); @@ -1390,9 +1405,6 @@ void TestMethods::invokeTests(QObject *testObject) const } QTestResult::finishedCurrentTestFunction(); QTestResult::setCurrentTestFunction(0); - QTestTable::clearGlobalTestTable(); - - QTestLog::stopLogging(); } #if defined(Q_OS_UNIX) @@ -1702,23 +1714,27 @@ static void initEnvironment() int QTest::qExec(QObject *testObject, int argc, char **argv) { - initEnvironment(); - QBenchmarkGlobalData benchmarkData; - QBenchmarkGlobalData::current = &benchmarkData; + qInit(testObject, argc, argv); + int ret = qRun(); + qCleanup(); + return ret; +} -#ifdef QTESTLIB_USE_VALGRIND - int callgrindChildExitCode = 0; -#endif +/*! \internal + */ +void QTest::qInit(QObject *testObject, int argc, char **argv) +{ + initEnvironment(); + QBenchmarkGlobalData::current = new QBenchmarkGlobalData; #if defined(Q_OS_MACX) - bool macNeedsActivate = qApp && (qstrcmp(qApp->metaObject()->className(), "QApplication") == 0); - IOPMAssertionID powerID; + macNeedsActivate = qApp && (qstrcmp(qApp->metaObject()->className(), "QApplication") == 0); // Don't restore saved window state for auto tests. QTestPrivate::disableWindowRestore(); -#endif -#ifndef QT_NO_EXCEPTIONS - try { + + // Disable App Nap which may cause tests to stall. + QTestPrivate::AppNapDisabler appNapDisabler; #endif #if defined(Q_OS_MACX) @@ -1749,6 +1765,24 @@ int QTest::qExec(QObject *testObject, int argc, char **argv) qtest_qParseArgs(argc, argv, false); + QTestTable::globalTestTable(); + QTestLog::startLogging(); +} + +/*! \internal + */ +int QTest::qRun() +{ + QTEST_ASSERT(currentTestObject); + +#ifdef QTESTLIB_USE_VALGRIND + int callgrindChildExitCode = 0; +#endif + +#ifndef QT_NO_EXCEPTIONS + try { +#endif + #if defined(Q_OS_WIN) if (!noCrashHandler) { # ifndef Q_CC_MINGW @@ -1784,17 +1818,17 @@ int QTest::qExec(QObject *testObject, int argc, char **argv) for (const QString &tf : qAsConst(QTest::testFunctions)) { const QByteArray tfB = tf.toLatin1(); const QByteArray signature = tfB + QByteArrayLiteral("()"); - QMetaMethod m = TestMethods::findMethod(testObject, signature.constData()); + QMetaMethod m = TestMethods::findMethod(currentTestObject, signature.constData()); if (!m.isValid() || !isValidSlot(m)) { fprintf(stderr, "Unknown test function: '%s'. Possible matches:\n", tfB.constData()); qPrintTestSlots(stderr, tfB.constData()); - fprintf(stderr, "\n%s -functions\nlists all available test functions.\n", argv[0]); + fprintf(stderr, "\n%s -functions\nlists all available test functions.\n", QTestResult::currentAppName()); exit(1); } commandLineMethods.push_back(m); } - TestMethods test(testObject, commandLineMethods); - test.invokeTests(testObject); + TestMethods test(currentTestObject, commandLineMethods); + test.invokeTests(currentTestObject); } #ifndef QT_NO_EXCEPTIONS @@ -1819,16 +1853,6 @@ int QTest::qExec(QObject *testObject, int argc, char **argv) } #endif - currentTestObject = 0; - - QSignalDumper::endDump(); - -#if defined(Q_OS_MACX) - if (macNeedsActivate) { - IOPMAssertionRelease(powerID); - } -#endif - #ifdef QTESTLIB_USE_VALGRIND if (QBenchmarkGlobalData::current->mode() == QBenchmarkGlobalData::CallgrindParentProcess) return callgrindChildExitCode; @@ -1838,6 +1862,26 @@ int QTest::qExec(QObject *testObject, int argc, char **argv) return qMin(QTestLog::failCount(), 127); } +/*! \internal + */ +void QTest::qCleanup() +{ + currentTestObject = 0; + + QTestTable::clearGlobalTestTable(); + QTestLog::stopLogging(); + + delete QBenchmarkGlobalData::current; + QBenchmarkGlobalData::current = 0; + + QSignalDumper::endDump(); + +#if defined(Q_OS_MACOS) + if (macNeedsActivate) + IOPMAssertionRelease(powerID); +#endif +} + /*! \overload \since 4.4 diff --git a/src/testlib/qtestcase.h b/src/testlib/qtestcase.h index a59eb4ecb3..2605325a94 100644 --- a/src/testlib/qtestcase.h +++ b/src/testlib/qtestcase.h @@ -147,6 +147,8 @@ do {\ } \ } +// Ideally we'd use qWaitFor instead of QTRY_LOOP_IMPL, but due +// to a compiler bug on MSVC < 2017 we can't (see QTBUG-59096) #define QTRY_IMPL(expr, timeout)\ const int qt_test_step = 50; \ const int qt_test_timeoutValue = timeout; \ @@ -259,12 +261,22 @@ namespace QTest return Internal::toString(t); } + template <typename T1, typename T2> + inline char *toString(const QPair<T1, T2> &pair); + + template <typename T1, typename T2> + inline char *toString(const std::pair<T1, T2> &pair); + Q_TESTLIB_EXPORT char *toHexRepresentation(const char *ba, int length); Q_TESTLIB_EXPORT char *toPrettyCString(const char *unicode, int length); - Q_TESTLIB_EXPORT char *toPrettyUnicode(const ushort *unicode, int length); + Q_TESTLIB_EXPORT char *toPrettyUnicode(QStringView string); Q_TESTLIB_EXPORT char *toString(const char *); Q_TESTLIB_EXPORT char *toString(const void *); + Q_TESTLIB_EXPORT void qInit(QObject *testObject, int argc = 0, char **argv = Q_NULLPTR); + Q_TESTLIB_EXPORT int qRun(); + Q_TESTLIB_EXPORT void qCleanup(); + Q_TESTLIB_EXPORT int qExec(QObject *testObject, int argc = 0, char **argv = Q_NULLPTR); Q_TESTLIB_EXPORT int qExec(QObject *testObject, const QStringList &arguments); @@ -319,6 +331,8 @@ namespace QTest Q_TESTLIB_EXPORT QTestData &newRow(const char *dataTag); Q_TESTLIB_EXPORT QTestData &addRow(const char *format, ...) Q_ATTRIBUTE_FORMAT_PRINTF(1, 2); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // kept after adding implementation of <T1, T2> out of paranoia: template <typename T> inline bool qCompare(T const &t1, T const &t2, const char *actual, const char *expected, const char *file, int line) @@ -326,6 +340,7 @@ namespace QTest return compare_helper(t1 == t2, "Compared values are not the same", toString(t1), toString(t2), actual, expected, file, line); } +#endif Q_TESTLIB_EXPORT bool qCompare(float const &t1, float const &t2, const char *actual, const char *expected, const char *file, int line); @@ -376,7 +391,12 @@ namespace QTest #endif template <typename T1, typename T2> - bool qCompare(T1 const &, T2 const &, const char *, const char *, const char *, int); + inline bool qCompare(const T1 &t1, const T2 &t2, const char *actual, const char *expected, + const char *file, int line) + { + return compare_helper(t1 == t2, "Compared values are not the same", + toString(t1), toString(t2), actual, expected, file, line); + } inline bool qCompare(double const &t1, float const &t2, const char *actual, const char *expected, const char *file, int line) diff --git a/src/testlib/qtestcase.qdoc b/src/testlib/qtestcase.qdoc index 8f3d140add..5b90419e28 100644 --- a/src/testlib/qtestcase.qdoc +++ b/src/testlib/qtestcase.qdoc @@ -82,11 +82,6 @@ QCOMPARE tries to output the contents of the values if the comparison fails, so it is visible from the test log why the comparison failed. - QCOMPARE is very strict on the data types. Both \a actual and \a expected - have to be of the same type, otherwise the test won't compile. This prohibits - unspecified behavior from being introduced; that is behavior that usually - occurs when the compiler implicitly casts the argument. - For your own classes, you can use \l QTest::toString() to format values for outputting into the test log. @@ -575,6 +570,15 @@ \sa QTest::keyClicks() */ +/*! \fn void QTest::keySequence(QWidget *widget, const QKeySequence &keySequence) + \overload + \since 5.10 + + Simulates typing of \a keySequence into a \a widget. + + \sa QTest::keyClick(), QTest::keyClicks() +*/ + /*! \fn void QTest::keyClick(QWindow *window, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay=-1) \overload \since 5.0 @@ -611,6 +615,15 @@ \sa QTest::keyClicks() */ +/*! \fn void QTest::keySequence(QWindow *window, const QKeySequence &keySequence) + \overload + \since 5.10 + + Simulates typing of \a keySequence into a \a window. + + \sa QTest::keyClick(), QTest::keyClicks() +*/ + /*! \fn void QTest::keyEvent(KeyAction action, QWidget *widget, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay=-1) Sends a Qt key event to \a widget with the given \a key and an associated \a action. @@ -1062,6 +1075,21 @@ \sa QTest::qSleep(), QSignalSpy::wait() */ +/*! \fn void QTest::qWaitFor(Functor predicate, int timeout) + + Waits for \a timeout milliseconds or until the \a predicate returns true. + + Returns \c true if the \a predicate returned true at any point, otherwise returns \c false. + + Example: + \snippet code/src_qtestlib_qtestcase.cpp 30 + + The code above will wait for the object to become ready, for a + maximum of three seconds. + + \since 5.10 +*/ + /*! \fn bool QTest::qWaitForWindowExposed(QWindow *window, int timeout) \since 5.0 @@ -1071,6 +1099,10 @@ This is mainly useful for asynchronous systems like X11, where a window will be mapped to screen some time after being asked to show itself on the screen. + Note that a window that is mapped to screen may still not be considered exposed if the window client + area is completely covered by other windows, or if the window is otherwise not visible. This function + will then time out when waiting for such a window. + \sa QTest::qWaitForWindowActive(), QWindow::isExposed() */ @@ -1093,6 +1125,13 @@ This is mainly useful for asynchronous systems like X11, where a window will be mapped to screen some time after being asked to show itself on the screen. + Note that a window that is mapped to screen may still not be considered exposed if the window client + area is completely covered by other windows, or if the window is otherwise not visible. This function + will then time out when waiting for such a window. + + A specific configuration where this happens is when using QGLWidget as a viewport widget on macOS: + The viewport widget gets the expose event, not the parent widget. + \sa QTest::qWaitForWindowActive() */ diff --git a/src/testlib/qtestkeyboard.h b/src/testlib/qtestkeyboard.h index e750fdb5a9..a7cf78f25a 100644 --- a/src/testlib/qtestkeyboard.h +++ b/src/testlib/qtestkeyboard.h @@ -54,6 +54,7 @@ #include <QtGui/qguiapplication.h> #include <QtGui/qwindow.h> #include <QtGui/qevent.h> +#include <QtGui/qkeysequence.h> #ifdef QT_WIDGETS_LIB #include <QtWidgets/qwidget.h> @@ -165,6 +166,15 @@ namespace QTest Q_DECL_UNUSED inline static void keyPress(QWindow *window, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay=-1) { keyEvent(Press, window, key, modifier, delay); } + Q_DECL_UNUSED inline static void keySequence(QWindow *window, const QKeySequence &keySequence) + { + for (int i = 0; i < keySequence.count(); ++i) { + const Qt::Key key = Qt::Key(keySequence[i] & ~Qt::KeyboardModifierMask); + const Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(keySequence[i] & Qt::KeyboardModifierMask); + keyClick(window, key, modifiers); + } + } + #ifdef QT_WIDGETS_LIB static void simulateEvent(QWidget *widget, bool press, int code, Qt::KeyboardModifiers modifier, QString text, bool repeat, int delay=-1) @@ -294,6 +304,16 @@ namespace QTest { keyEvent(Release, widget, key, modifier, delay); } inline static void keyClick(QWidget *widget, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay=-1) { keyEvent(Click, widget, key, modifier, delay); } + + inline static void keySequence(QWidget *widget, const QKeySequence &keySequence) + { + for (int i = 0; i < keySequence.count(); ++i) { + const Qt::Key key = Qt::Key(keySequence[i] & ~Qt::KeyboardModifierMask); + const Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(keySequence[i] & Qt::KeyboardModifierMask); + keyClick(widget, key, modifiers); + } + } + #endif // QT_WIDGETS_LIB } diff --git a/src/testlib/qtestsystem.h b/src/testlib/qtestsystem.h index f38a156936..79fe68004e 100644 --- a/src/testlib/qtestsystem.h +++ b/src/testlib/qtestsystem.h @@ -54,8 +54,46 @@ QT_BEGIN_NAMESPACE namespace QTest { + template <typename Functor> + Q_REQUIRED_RESULT static bool qWaitFor(Functor predicate, int timeout = 5000) + { + // We should not spin the event loop in case the predicate is already true, + // otherwise we might send new events that invalidate the predicate. + if (predicate()) + return true; + + // qWait() is expected to spin the event loop, even when called with a small + // timeout like 1ms, so we we can't use a simple while-loop here based on + // the deadline timer not having timed out. Use do-while instead. + + int remaining = timeout; + QDeadlineTimer deadline(remaining, Qt::PreciseTimer); + + do { + QCoreApplication::processEvents(QEventLoop::AllEvents, remaining); + QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); + + remaining = deadline.remainingTime(); + if (remaining > 0) { + QTest::qSleep(qMin(10, remaining)); + remaining = deadline.remainingTime(); + } + + if (predicate()) + return true; + + remaining = deadline.remainingTime(); + } while (remaining > 0); + + return predicate(); // Last chance + } + Q_DECL_UNUSED inline static void qWait(int ms) { + // Ideally this method would be implemented in terms of qWaitFor, with + // a predicate that always returns false, but due to a compiler bug in + // GCC 6 we can't do that. + Q_ASSERT(QCoreApplication::instance()); QDeadlineTimer timer(ms, Qt::PreciseTimer); @@ -72,23 +110,17 @@ namespace QTest } #ifdef QT_GUI_LIB - inline static bool qWaitForWindowActive(QWindow *window, int timeout = 5000) + Q_REQUIRED_RESULT inline static bool qWaitForWindowActive(QWindow *window, int timeout = 5000) { - QDeadlineTimer timer(timeout, Qt::PreciseTimer); - int remaining = timeout; - while (!window->isActive() && remaining > 0) { - QCoreApplication::processEvents(QEventLoop::AllEvents, remaining); - QCoreApplication::sendPostedEvents(Q_NULLPTR, QEvent::DeferredDelete); - QTest::qSleep(10); - remaining = timer.remainingTime(); - } + bool becameActive = qWaitFor([&]() { return window->isActive(); }, timeout); + // Try ensuring the platform window receives the real position. // (i.e. that window->pos() reflects reality) // isActive() ( == FocusIn in case of X) does not guarantee this. It seems some WMs randomly // send the final ConfigureNotify (the one with the non-bogus 0,0 position) after the FocusIn. // If we just let things go, every mapTo/FromGlobal call the tests perform directly after // qWaitForWindowShown() will generate bogus results. - if (window->isActive()) { + if (becameActive) { int waitNo = 0; // 0, 0 might be a valid position after all, so do not wait for ever while (window->position().isNull()) { if (waitNo++ > timeout / 10) @@ -99,29 +131,21 @@ namespace QTest return window->isActive(); } - inline static bool qWaitForWindowExposed(QWindow *window, int timeout = 5000) + Q_REQUIRED_RESULT inline static bool qWaitForWindowExposed(QWindow *window, int timeout = 5000) { - QDeadlineTimer timer(timeout, Qt::PreciseTimer); - int remaining = timeout; - while (!window->isExposed() && remaining > 0) { - QCoreApplication::processEvents(QEventLoop::AllEvents, remaining); - QCoreApplication::sendPostedEvents(Q_NULLPTR, QEvent::DeferredDelete); - QTest::qSleep(10); - remaining = timer.remainingTime(); - } - return window->isExposed(); + return qWaitFor([&]() { return window->isExposed(); }, timeout); } #endif #ifdef QT_WIDGETS_LIB - inline static bool qWaitForWindowActive(QWidget *widget, int timeout = 5000) + Q_REQUIRED_RESULT inline static bool qWaitForWindowActive(QWidget *widget, int timeout = 5000) { if (QWindow *window = widget->window()->windowHandle()) return qWaitForWindowActive(window, timeout); return false; } - inline static bool qWaitForWindowExposed(QWidget *widget, int timeout = 5000) + Q_REQUIRED_RESULT inline static bool qWaitForWindowExposed(QWidget *widget, int timeout = 5000) { if (QWindow *window = widget->window()->windowHandle()) return qWaitForWindowExposed(window, timeout); @@ -131,7 +155,8 @@ namespace QTest #if QT_DEPRECATED_SINCE(5, 0) # ifdef QT_WIDGETS_LIB - QT_DEPRECATED inline static bool qWaitForWindowShown(QWidget *widget, int timeout = 5000) + + QT_DEPRECATED Q_REQUIRED_RESULT inline static bool qWaitForWindowShown(QWidget *widget, int timeout = 5000) { return qWaitForWindowExposed(widget, timeout); } diff --git a/src/testlib/qtestutil_macos.mm b/src/testlib/qtestutil_macos.mm index 70a7fb9f85..7579c3b164 100644 --- a/src/testlib/qtestutil_macos.mm +++ b/src/testlib/qtestutil_macos.mm @@ -54,6 +54,33 @@ namespace QTestPrivate { void disableWindowRestore() { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"ApplePersistenceIgnoreState"]; } + + /*! \internal + \class AppNapDisabler + \brief Disables App Nap by registereing a bacground activity. + + App Nap remains disabled as long as the AppNapDisabler instance + exists. + */ + + /*! \internal + Creates an AppNapDisabler instance and starts a NSActivityBackground activity. + */ + AppNapDisabler::AppNapDisabler() + { + m_activity = [[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityBackground + reason:@"Qt Auto Test"]; + [m_activity retain]; + } + + /*! \internal + Destroys the AppNapDisabler instance and ends the NSActivityBackground activity. + */ + AppNapDisabler::~AppNapDisabler() + { + [[NSProcessInfo processInfo] endActivity:m_activity]; + [m_activity release]; + } } QT_END_NAMESPACE diff --git a/src/testlib/qtestutil_macos_p.h b/src/testlib/qtestutil_macos_p.h index d7cc5bc251..36f27167c0 100644 --- a/src/testlib/qtestutil_macos_p.h +++ b/src/testlib/qtestutil_macos_p.h @@ -58,6 +58,17 @@ QT_BEGIN_NAMESPACE namespace QTestPrivate { void disableWindowRestore(); + + class AppNapDisabler + { + public: + AppNapDisabler(); + ~AppNapDisabler(); + AppNapDisabler(const AppNapDisabler&) = delete; + AppNapDisabler& operator=(const AppNapDisabler&) = delete; + private: + id m_activity; + }; } QT_END_NAMESPACE |