diff options
Diffstat (limited to 'src/testlib')
-rw-r--r-- | src/testlib/doc/src/qt-webpages.qdoc | 4 | ||||
-rw-r--r-- | src/testlib/doc/src/qttest-best-practices.qdoc | 2 | ||||
-rw-r--r-- | src/testlib/doc/src/qttestlib-manual.qdoc | 47 | ||||
-rw-r--r-- | src/testlib/qabstractitemmodeltester.cpp | 17 | ||||
-rw-r--r-- | src/testlib/qabstracttestlogger.cpp | 5 | ||||
-rw-r--r-- | src/testlib/qabstracttestlogger_p.h | 10 | ||||
-rw-r--r-- | src/testlib/qplaintestlogger.cpp | 27 | ||||
-rw-r--r-- | src/testlib/qplaintestlogger_p.h | 7 | ||||
-rw-r--r-- | src/testlib/qteamcitylogger.cpp | 5 | ||||
-rw-r--r-- | src/testlib/qtestblacklist.cpp | 6 | ||||
-rw-r--r-- | src/testlib/qtestcase.cpp | 90 | ||||
-rw-r--r-- | src/testlib/qtestcase.h | 6 | ||||
-rw-r--r-- | src/testlib/qtestcase.qdoc | 35 | ||||
-rw-r--r-- | src/testlib/qtestdata.h | 8 | ||||
-rw-r--r-- | src/testlib/qtestlog.cpp | 39 | ||||
-rw-r--r-- | src/testlib/qtestlog_p.h | 2 | ||||
-rw-r--r-- | src/testlib/qtestresult.cpp | 6 | ||||
-rw-r--r-- | src/testlib/qtestutil_macos.mm | 7 | ||||
-rw-r--r-- | src/testlib/selfcover.pri | 4 |
19 files changed, 244 insertions, 83 deletions
diff --git a/src/testlib/doc/src/qt-webpages.qdoc b/src/testlib/doc/src/qt-webpages.qdoc index 976435e668..aa907f3685 100644 --- a/src/testlib/doc/src/qt-webpages.qdoc +++ b/src/testlib/doc/src/qt-webpages.qdoc @@ -30,8 +30,8 @@ */ /*! - \externalpage https://www.froglogic.com/coco/ - \title Froglogic Coco Code Coverage + \externalpage https://www.qt.io/product/quality-assurance/coco + \title Coco */ /*! diff --git a/src/testlib/doc/src/qttest-best-practices.qdoc b/src/testlib/doc/src/qttest-best-practices.qdoc index 7143e644fd..da03399668 100644 --- a/src/testlib/doc/src/qttest-best-practices.qdoc +++ b/src/testlib/doc/src/qttest-best-practices.qdoc @@ -184,7 +184,7 @@ \section2 Use Coverage Tools - Use a coverage tool such as \l {Froglogic Coco Code Coverage} or \l {gcov} + Use a coverage tool such as \l {Coco} or \l {gcov} to help write tests that cover as many statements, branches, and conditions as possible in the function or class being tested. The earlier this is done in the development cycle for a new feature, the easier it will be to catch diff --git a/src/testlib/doc/src/qttestlib-manual.qdoc b/src/testlib/doc/src/qttestlib-manual.qdoc index 8ff82ba28f..cae17c347b 100644 --- a/src/testlib/doc/src/qttestlib-manual.qdoc +++ b/src/testlib/doc/src/qttestlib-manual.qdoc @@ -134,6 +134,47 @@ For more examples, refer to the \l{Qt Test Tutorial}. + \section1 Increasing Test Function Timeout + + QtTest limits the run-time of each test to catch infinite loops and similar + bugs. By default, any test function call will be interrupted after five + minutes. For data-driven tests, this applies to each call with a distinct + data-tag. This timeout can be configured by setting the \c QTEST_FUNCTION_TIMEOUT + environment variable to the maximum number of milliseconds that is acceptable + for a single call to take. If a test takes longer than the configured timeout, + it is interrupted, and \c qFatal() is called. As a result, the test aborts by + default, as if it had crashed. + + To set \c QTEST_FUNCTION_TIMEOUT from the command line on Linux or macOS, enter: + + \badcode + QTEST_FUNCTION_TIMEOUT=900000 + export QTEST_FUNCTION_TIMEOUT + \endcode + + On Windows: + \badcode + SET QTEST_FUNCTION_TIMEOUT=900000 + \endcode + + Then run the test inside this environment. + + Alternatively, you can set the environment variable programmatically in the + test code itself, for example by calling, from the + \l{Creating a Test}{initMain()} special method of your test class: + + \badcode + qputenv("QTEST_FUNCTION_TIMEOUT", "900000"); + \endcode + + To calculate a suitable value for the timeout, see how long the test usually + takes and decide how much longer it can take without that being a symptom of + some problem. Convert that longer time to milliseconds to get the timeout value. + For example, if you decide that a test that takes several minutes could + reasonably take up to twenty minutes, for example on a slow machine, + multiply \c{20 * 60 * 1000 = 1200000} and set the environment variable to + \c 1200000 instead of the \c 900000 above. + \if !defined(qtforpython) \section1 Building a Test @@ -154,6 +195,12 @@ All labeled targets will be run when \c {test} target is called on the command line. + \note On Android, if you have one connected device or emulator, tests will + run on that device. If you have more than one device connected, set the + environment variable \c {ANDROID_DEVICE_SERIAL} to the + \l {Android: Query for devices}{ADB serial number} of the device that + you want to run tests on. + There are several other advantages with CMake. For example, the result of a test run can be published on a web server using CDash with virtually no effort. diff --git a/src/testlib/qabstractitemmodeltester.cpp b/src/testlib/qabstractitemmodeltester.cpp index 4c86d65e77..1cd18b98bb 100644 --- a/src/testlib/qabstractitemmodeltester.cpp +++ b/src/testlib/qabstractitemmodeltester.cpp @@ -438,7 +438,7 @@ void QAbstractItemModelTesterPrivate::parent() // when asked for the parent of an invalid index. MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid()); - if (!model->hasChildren()) + if (model->rowCount() == 0 || model->columnCount() == 0) return; // Column 0 | Column 1 | @@ -449,11 +449,12 @@ void QAbstractItemModelTesterPrivate::parent() // Common error test #1, make sure that a top level index has a parent // that is a invalid QModelIndex. QModelIndex topIndex = model->index(0, 0, QModelIndex()); + MODELTESTER_VERIFY(topIndex.isValid()); MODELTESTER_VERIFY(!model->parent(topIndex).isValid()); // Common error test #2, make sure that a second level index has a parent // that is the first level index. - if (model->hasChildren(topIndex)) { + if (model->rowCount(topIndex) > 0) { QModelIndex childIndex = model->index(0, 0, topIndex); MODELTESTER_VERIFY(childIndex.isValid()); MODELTESTER_COMPARE(model->parent(childIndex), topIndex); @@ -465,7 +466,7 @@ void QAbstractItemModelTesterPrivate::parent() if (model->hasIndex(0, 1)) { QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); MODELTESTER_VERIFY(topIndex1.isValid()); - if (model->hasChildren(topIndex) && model->hasChildren(topIndex1)) { + if (model->rowCount(topIndex) > 0 && model->rowCount(topIndex1) > 0) { QModelIndex childIndex = model->index(0, 0, topIndex); MODELTESTER_VERIFY(childIndex.isValid()); QModelIndex childIndex1 = model->index(0, 0, topIndex1); @@ -569,7 +570,7 @@ void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, i // recursively go down the children if (model->hasChildren(index) && currentDepth < 10) - checkChildren(index, ++currentDepth); + checkChildren(index, currentDepth + 1); // make sure that after testing the children that the index doesn't change. QModelIndex newerIndex = model->index(r, c, parent); @@ -583,7 +584,7 @@ void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, i */ void QAbstractItemModelTesterPrivate::data() { - if (!model->hasChildren()) + if (model->rowCount() == 0 || model->columnCount() == 0) return; MODELTESTER_VERIFY(model->index(0, 0).isValid()); @@ -719,12 +720,12 @@ void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &pa Changing c; c.parent = parent; c.oldSize = model->rowCount(parent); - if (start > 0) { + if (start > 0 && model->columnCount(parent) > 0) { const QModelIndex startIndex = model->index(start - 1, 0, parent); MODELTESTER_VERIFY(startIndex.isValid()); c.last = model->data(startIndex); } - if (end < c.oldSize - 1) { + if (end < c.oldSize - 1 && model->columnCount(parent) > 0) { const QModelIndex endIndex = model->index(end + 1, 0, parent); MODELTESTER_VERIFY(endIndex.isValid()); c.next = model->data(endIndex); @@ -844,3 +845,5 @@ bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2, QT_END_NAMESPACE + +#include "moc_qabstractitemmodeltester.cpp" diff --git a/src/testlib/qabstracttestlogger.cpp b/src/testlib/qabstracttestlogger.cpp index ff05dd88c7..c7a0784da7 100644 --- a/src/testlib/qabstracttestlogger.cpp +++ b/src/testlib/qabstracttestlogger.cpp @@ -90,6 +90,11 @@ QAbstractTestLogger::~QAbstractTestLogger() stream = nullptr; } +bool QAbstractTestLogger::isLoggingToStdout() const +{ + return stream == stdout; +} + void QAbstractTestLogger::filterUnprintable(char *str) const { unsigned char *idx = reinterpret_cast<unsigned char *>(str); diff --git a/src/testlib/qabstracttestlogger_p.h b/src/testlib/qabstracttestlogger_p.h index e5a1404c16..06a39ee65a 100644 --- a/src/testlib/qabstracttestlogger_p.h +++ b/src/testlib/qabstracttestlogger_p.h @@ -51,7 +51,8 @@ // We mean it. // -#include <qglobal.h> +#include <QtTest/qttestglobal.h> + #include <stdio.h> #include <stdlib.h> @@ -60,8 +61,9 @@ QT_BEGIN_NAMESPACE class QBenchmarkResult; class QTestData; -class QAbstractTestLogger +class Q_TESTLIB_EXPORT QAbstractTestLogger { + Q_DISABLE_COPY_MOVE(QAbstractTestLogger) public: enum IncidentTypes { Pass, @@ -106,6 +108,8 @@ public: virtual void addMessage(MessageTypes type, const QString &message, const char *file = nullptr, int line = 0) = 0; + bool isLoggingToStdout() const; + void outputString(const char *msg); protected: @@ -182,7 +186,7 @@ namespace QTest namespace QTestPrivate { enum IdentifierPart { TestObject = 0x1, TestFunction = 0x2, TestDataTag = 0x4, AllParts = 0xFFFF }; - void generateTestIdentifier(QTestCharBuffer *identifier, int parts = AllParts); + void Q_TESTLIB_EXPORT generateTestIdentifier(QTestCharBuffer *identifier, int parts = AllParts); } QT_END_NAMESPACE diff --git a/src/testlib/qplaintestlogger.cpp b/src/testlib/qplaintestlogger.cpp index 5d9283d8e5..851a284678 100644 --- a/src/testlib/qplaintestlogger.cpp +++ b/src/testlib/qplaintestlogger.cpp @@ -225,7 +225,8 @@ void QPlainTestLogger::outputMessage(const char *str) outputString(str); } -void QPlainTestLogger::printMessage(const char *type, const char *msg, const char *file, int line) +void QPlainTestLogger::printMessage(MessageSource source, const char *type, const char *msg, + const char *file, int line) { QTEST_ASSERT(type); QTEST_ASSERT(msg); @@ -233,13 +234,23 @@ void QPlainTestLogger::printMessage(const char *type, const char *msg, const cha QTestCharBuffer messagePrefix; QTestCharBuffer failureLocation; - if (file) { #ifdef Q_OS_WIN -#define FAILURE_LOCATION_STR "\n%s(%d) : failure location" + constexpr const char *INCIDENT_LOCATION_STR = "\n%s(%d) : failure location"; + constexpr const char *OTHER_LOCATION_STR = "\n%s(%d) : message location"; #else -#define FAILURE_LOCATION_STR "\n Loc: [%s(%d)]" + constexpr const char *INCIDENT_LOCATION_STR = "\n Loc: [%s(%d)]"; + constexpr const char *OTHER_LOCATION_STR = INCIDENT_LOCATION_STR; #endif - QTest::qt_asprintf(&failureLocation, FAILURE_LOCATION_STR, file, line); + + if (file) { + switch (source) { + case MessageSource::Incident: + QTest::qt_asprintf(&failureLocation, INCIDENT_LOCATION_STR, file, line); + break; + case MessageSource::Other: + QTest::qt_asprintf(&failureLocation, OTHER_LOCATION_STR, file, line); + break; + } } const char *msgFiller = msg[0] ? " " : ""; @@ -360,7 +371,7 @@ void QPlainTestLogger::stopLogging() void QPlainTestLogger::enterTestFunction(const char * /*function*/) { if (QTestLog::verboseLevel() >= 1) - printMessage(QTest::messageType2String(Info), "entering"); + printMessage(MessageSource::Other, QTest::messageType2String(Info), "entering"); } void QPlainTestLogger::leaveTestFunction() @@ -375,7 +386,7 @@ void QPlainTestLogger::addIncident(IncidentTypes type, const char *description, && QTestLog::verboseLevel() < 0) return; - printMessage(QTest::incidentType2String(type), description, file, line); + printMessage(MessageSource::Incident, QTest::incidentType2String(type), description, file, line); } void QPlainTestLogger::addBenchmarkResult(const QBenchmarkResult &result) @@ -399,7 +410,7 @@ void QPlainTestLogger::addMessage(MessageTypes type, const QString &message, if (type != QAbstractTestLogger::QFatal && QTestLog::verboseLevel() < 0) return; - printMessage(QTest::messageType2String(type), qPrintable(message), file, line); + printMessage(MessageSource::Other, QTest::messageType2String(type), qPrintable(message), file, line); } QT_END_NAMESPACE diff --git a/src/testlib/qplaintestlogger_p.h b/src/testlib/qplaintestlogger_p.h index 80ef4864c1..1a19b99442 100644 --- a/src/testlib/qplaintestlogger_p.h +++ b/src/testlib/qplaintestlogger_p.h @@ -78,7 +78,12 @@ public: const char *file = nullptr, int line = 0) override; private: - void printMessage(const char *type, const char *msg, const char *file = nullptr, int line = 0); + enum class MessageSource { + Incident, + Other, + }; + void printMessage(MessageSource source, const char *type, const char *msg, + const char *file = nullptr, int line = 0); void outputMessage(const char *str); void printBenchmarkResult(const QBenchmarkResult &result); }; diff --git a/src/testlib/qteamcitylogger.cpp b/src/testlib/qteamcitylogger.cpp index 8a77143454..1afec2943a 100644 --- a/src/testlib/qteamcitylogger.cpp +++ b/src/testlib/qteamcitylogger.cpp @@ -268,9 +268,8 @@ void QTeamCityLogger::addPendingMessage(const char *type, const QString &msg, co if (file) { pendMessage += QString(QLatin1String("%1 |[Loc: %2(%3)|]: %4")) - .arg(QString::fromUtf8(type), QString::fromUtf8(file)) - .arg(line) - .arg(msg); + .arg(QString::fromUtf8(type), QString::fromUtf8(file), + QString::number(line), msg); } else { diff --git a/src/testlib/qtestblacklist.cpp b/src/testlib/qtestblacklist.cpp index ac4793eebf..850f1d779f 100644 --- a/src/testlib/qtestblacklist.cpp +++ b/src/testlib/qtestblacklist.cpp @@ -46,6 +46,7 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/qvariant.h> #include <QtCore/QSysInfo> +#include <QtCore/QOperatingSystemVersion> #include <set> @@ -208,6 +209,11 @@ static QSet<QByteArray> activeConditions() if (!distributionName.isEmpty()) { if (result.find(distributionName) == result.end()) result.insert(distributionName); + if (distributionName == "macos" || distributionName == "osx") { + const auto version = QOperatingSystemVersion::current(); + if (version.majorVersion() >= 11) + distributionRelease = QByteArray::number(version.majorVersion()); + } if (!distributionRelease.isEmpty()) { QByteArray versioned = distributionName + "-" + distributionRelease; if (result.find(versioned) == result.end()) diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp index 74507c11e1..2c023a8ee1 100644 --- a/src/testlib/qtestcase.cpp +++ b/src/testlib/qtestcase.cpp @@ -82,10 +82,10 @@ #include <QtTest/private/qappletestlogger_p.h> #endif +#include <atomic> #include <cmath> #include <numeric> #include <algorithm> -#include <condition_variable> #include <mutex> #include <chrono> @@ -212,13 +212,13 @@ static void stackTrace() if (debuggerPresent() || hasSystemCrashReporter()) return; -#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) +#if defined(Q_OS_LINUX) || (defined(Q_OS_MACOS) && !defined(Q_PROCESSOR_ARM_64)) const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime()); const int msecsTotalTime = qRound(QTestLog::msecsTotalTime()); fprintf(stderr, "\n=== Received signal at function time: %dms, total time: %dms, dumping stack ===\n", msecsFunctionTime, msecsTotalTime); -#endif -#ifdef Q_OS_LINUX + +# ifdef Q_OS_LINUX char cmd[512]; qsnprintf(cmd, 512, "gdb --pid %d 2>/dev/null <<EOF\n" "set prompt\n" @@ -231,7 +231,7 @@ static void stackTrace() if (system(cmd) == -1) fprintf(stderr, "calling gdb failed\n"); fprintf(stderr, "=== End of stack trace ===\n"); -#elif defined(Q_OS_MACOS) +# elif defined(Q_OS_MACOS) char cmd[512]; qsnprintf(cmd, 512, "lldb -p %d 2>/dev/null <<EOF\n" "bt all\n" @@ -241,6 +241,8 @@ static void stackTrace() if (system(cmd) == -1) fprintf(stderr, "calling lldb failed\n"); fprintf(stderr, "=== End of stack trace ===\n"); +# endif + #endif } @@ -1005,16 +1007,30 @@ void TestMethods::invokeTestOnData(int index) const class WatchDog : public QThread { - enum Expectation { + enum Expectation : std::size_t { + // bits 0..1: state ThreadStart, TestFunctionStart, TestFunctionEnd, ThreadEnd, + ExpectationMask = ThreadStart | TestFunctionStart | TestFunctionEnd | ThreadEnd, + + // bits 2..: generation }; + Q_STATIC_ASSERT(size_t(ExpectationMask) == 0x3); + // static constexpr size_t GenerationShift = 2; // C++17-ism, so inline in combine and generation. + + static constexpr Expectation state(Expectation e) noexcept + { return static_cast<Expectation>(e & ExpectationMask); } + static constexpr size_t generation(Expectation e) noexcept + { return e >> 2; } + static constexpr Expectation combine(Expectation e, size_t gen) noexcept + { return static_cast<Expectation>(e | (gen << 2)); } - bool waitFor(std::unique_lock<QtPrivate::mutex> &m, Expectation e) { - auto expectationChanged = [this, e] { return expecting != e; }; - switch (e) { + bool waitFor(std::unique_lock<QtPrivate::mutex> &m, Expectation e) + { + auto expectationChanged = [this, e] { return expecting.load(std::memory_order_relaxed) != e; }; + switch (state(e)) { case TestFunctionEnd: return waitCondition.wait_for(m, defaultTimeout(), expectationChanged); case ThreadStart: @@ -1027,48 +1043,59 @@ class WatchDog : public QThread return false; } + void setExpectation(Expectation e) + { + Q_ASSERT(generation(e) == 0); // no embedded generation allowed + const auto locker = qt_scoped_lock(mutex); + auto cur = expecting.load(std::memory_order_relaxed); + auto gen = generation(cur); + if (e == TestFunctionStart) + ++gen; + e = combine(e, gen); + expecting.store(e, std::memory_order_relaxed); + waitCondition.notify_all(); + } + public: WatchDog() { auto locker = qt_unique_lock(mutex); - expecting = ThreadStart; + expecting.store(ThreadStart, std::memory_order_relaxed); start(); waitFor(locker, ThreadStart); } - ~WatchDog() { - { - const auto locker = qt_scoped_lock(mutex); - expecting = ThreadEnd; - waitCondition.notify_all(); - } + + ~WatchDog() + { + setExpectation(ThreadEnd); wait(); } - void beginTest() { - const auto locker = qt_scoped_lock(mutex); - expecting = TestFunctionEnd; - waitCondition.notify_all(); + void beginTest() + { + setExpectation(TestFunctionEnd); } - void testFinished() { - const auto locker = qt_scoped_lock(mutex); - expecting = TestFunctionStart; - waitCondition.notify_all(); + void testFinished() + { + setExpectation(TestFunctionStart); } - void run() override { + void run() override + { auto locker = qt_unique_lock(mutex); - expecting = TestFunctionStart; + expecting.store(TestFunctionStart, std::memory_order_release); waitCondition.notify_all(); while (true) { - switch (expecting) { + Expectation e = expecting.load(std::memory_order_acquire); + switch (state(e)) { case ThreadEnd: return; case ThreadStart: Q_UNREACHABLE(); case TestFunctionStart: case TestFunctionEnd: - if (Q_UNLIKELY(!waitFor(locker, expecting))) { + if (Q_UNLIKELY(!waitFor(locker, e))) { stackTrace(); qFatal("Test function timed out"); } @@ -1079,7 +1106,7 @@ public: private: QtPrivate::mutex mutex; QtPrivate::condition_variable waitCondition; - Expectation expecting; + std::atomic<Expectation> expecting; }; #else // !QT_CONFIG(thread) @@ -2798,6 +2825,11 @@ char *QTest::toString(const char *str) /*! \internal */ +char *QTest::toString(const volatile void *p) // Use volatile to match compare_ptr_helper() +{ + return QTest::toString(const_cast<const void *>(p)); +} + char *QTest::toString(const void *p) { char *msg = new char[128]; diff --git a/src/testlib/qtestcase.h b/src/testlib/qtestcase.h index e1518708e8..6c9d23f649 100644 --- a/src/testlib/qtestcase.h +++ b/src/testlib/qtestcase.h @@ -101,6 +101,7 @@ do {\ " but no exception caught", __FILE__, __LINE__);\ return;\ } QT_CATCH (const exceptiontype &) {\ + /* success */\ }\ } QT_CATCH (const std::exception &e) {\ QByteArray msg = QByteArray() + "Expected exception of type " #exceptiontype \ @@ -110,7 +111,7 @@ do {\ } QT_CATCH (...) {\ QTest::qFail("Expected exception of type " #exceptiontype " to be thrown" \ " but unknown exception caught", __FILE__, __LINE__);\ - return;\ + QT_RETHROW;\ }\ } while (false) @@ -283,7 +284,8 @@ namespace QTest Q_TESTLIB_EXPORT char *toPrettyCString(const char *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 char *toString(const volatile void *); + Q_TESTLIB_EXPORT char *toString(const void *); // ### FIXME: Qt 7: Remove Q_TESTLIB_EXPORT void qInit(QObject *testObject, int argc = 0, char **argv = nullptr); Q_TESTLIB_EXPORT int qRun(); diff --git a/src/testlib/qtestcase.qdoc b/src/testlib/qtestcase.qdoc index 72f8cdaf8c..dd5ec762a7 100644 --- a/src/testlib/qtestcase.qdoc +++ b/src/testlib/qtestcase.qdoc @@ -149,15 +149,24 @@ \relates QTest - The QVERIFY_EXCEPTION_THROWN macro executes an \a expression and tries - to catch an exception thrown from the \a expression. If the \a expression - throws an exception and its type is the same as \a exceptiontype - or \a exceptiontype is substitutable with the type of thrown exception - (i.e. usually the type of thrown exception is publicly derived - from \a exceptiontype) then execution will be continued. If not-substitutable - type of exception is thrown or the \a expression doesn't throw an exception - at all, then a failure will be recorded in the test log and - the test won't be executed further. + The QVERIFY_EXCEPTION_THROWN macro executes \a expression + and tries to catch an exception thrown from \a expression. + + There are several possible outcomes: + + \list + \li If \a expression throws an exception that is either the same as + \a exceptiontype or derived from \a exceptiontype, then execution will continue. + + \li Otherwise, if \a expression throws no exception, or the + exception thrown derives from \c{std::exception}, then a failure + will be recorded in the test log and the macro returns early + (from enclosing function). + + \li If the thrown exception derives neither from \c{std::exception} nor from + \a exceptiontype, a failure will be recorded in the test log, and the exception is + re-thrown. This avoids problems with e.g. pthread cancellation exceptions. + \endlist \note This macro can only be used in a test function that is invoked by the test framework. @@ -1258,7 +1267,7 @@ should typically use createTouchDevice() to initialize a QTouchDevice member variable in your test case class, and use the same instance for all tests. - \sa QTest::QTouchEventSequence + \sa QTest::QTouchEventSequence, touchEvent() */ /*! @@ -1394,6 +1403,9 @@ QTouchEventSequence is called (ie when the object returned runs out of scope), unless \a autoCommit is set to false. When \a autoCommit is false, commit() has to be called manually. + + \l createTouchDevice() can be called to create a test touch device for use with this + function. */ /*! @@ -1410,6 +1422,9 @@ QTouchEventSequence is called (ie when the object returned runs out of scope), unless \a autoCommit is set to false. When \a autoCommit is false, commit() has to be called manually. + + \l createTouchDevice() can be called to create a test touch device for use with this + function. */ // Internals of qtestmouse.h: diff --git a/src/testlib/qtestdata.h b/src/testlib/qtestdata.h index cf10fed8f3..ed81a9de6e 100644 --- a/src/testlib/qtestdata.h +++ b/src/testlib/qtestdata.h @@ -85,6 +85,14 @@ inline QTestData &operator<<(QTestData &data, const char * value) return data; } +#ifdef __cpp_char8_t +Q_WEAK_OVERLOAD +inline QTestData &operator<<(QTestData &data, const char8_t *value) +{ + return data << reinterpret_cast<const char *>(value); +} +#endif + #ifdef QT_USE_QSTRINGBUILDER template<typename A, typename B> inline QTestData &operator<<(QTestData &data, const QStringBuilder<A, B> &value) diff --git a/src/testlib/qtestlog.cpp b/src/testlib/qtestlog.cpp index be50176a08..8917f0dc0e 100644 --- a/src/testlib/qtestlog.cpp +++ b/src/testlib/qtestlog.cpp @@ -99,7 +99,7 @@ static void saveCoverageTool(const char * appname, bool testfailed, bool install static QElapsedTimer elapsedFunctionTime; static QElapsedTimer elapsedTotalTime; -#define FOREACH_TEST_LOGGER for (QAbstractTestLogger *logger : QTest::loggers) +#define FOREACH_TEST_LOGGER for (QAbstractTestLogger *logger : *QTest::loggers()) namespace QTest { @@ -168,8 +168,7 @@ namespace QTest { static IgnoreResultList *ignoreResultList = nullptr; - static QVector<QAbstractTestLogger*> loggers; - static bool loggerUsingStdout = false; + Q_GLOBAL_STATIC(QVector<QAbstractTestLogger *>, loggers) static int verbosity = 0; static int maxWarnings = 2002; @@ -339,6 +338,8 @@ void QTestLog::addXFail(const char *msg, const char *file, int line) QTEST_ASSERT(msg); QTEST_ASSERT(file); + // Will be counted in addPass() if we get there. + FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::XFail, msg, file, line); } @@ -391,7 +392,7 @@ void QTestLog::addBXFail(const char *msg, const char *file, int line) QTEST_ASSERT(msg); QTEST_ASSERT(file); - ++QTest::blacklists; + // Will be counted in addBPass() if we get there. FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::BlacklistedXFail, msg, file, line); @@ -430,8 +431,7 @@ void QTestLog::stopLogging() logger->stopLogging(); delete logger; } - QTest::loggers.clear(); - QTest::loggerUsingStdout = false; + QTest::loggers()->clear(); saveCoverageTool(QTestResult::currentAppName(), failCount() != 0, QTestLog::installedTestCoverage()); } @@ -439,8 +439,6 @@ void QTestLog::addLogger(LogMode mode, const char *filename) { if (filename && strcmp(filename, "-") == 0) filename = nullptr; - if (!filename) - QTest::loggerUsingStdout = true; QAbstractTestLogger *logger = nullptr; switch (mode) { @@ -478,17 +476,36 @@ void QTestLog::addLogger(LogMode mode, const char *filename) } QTEST_ASSERT(logger); - QTest::loggers.append(logger); + addLogger(logger); +} + +/*! + \internal + + Adds a new logger to the set of loggers that will be used + to report incidents and messages during testing. + + The function takes ownership of the logger. +*/ +void QTestLog::addLogger(QAbstractTestLogger *logger) +{ + QTEST_ASSERT(logger); + QTest::loggers()->append(logger); } int QTestLog::loggerCount() { - return QTest::loggers.size(); + return QTest::loggers()->size(); } bool QTestLog::loggerUsingStdout() { - return QTest::loggerUsingStdout; + FOREACH_TEST_LOGGER { + if (logger->isLoggingToStdout()) + return true; + } + + return false; } void QTestLog::warn(const char *msg, const char *file, int line) diff --git a/src/testlib/qtestlog_p.h b/src/testlib/qtestlog_p.h index ddaf14ed9b..bdb22acbd3 100644 --- a/src/testlib/qtestlog_p.h +++ b/src/testlib/qtestlog_p.h @@ -64,6 +64,7 @@ QT_BEGIN_NAMESPACE class QBenchmarkResult; class QRegularExpression; class QTestData; +class QAbstractTestLogger; class Q_TESTLIB_EXPORT QTestLog { @@ -115,6 +116,7 @@ public: static void stopLogging(); static void addLogger(LogMode mode, const char *filename); + static void addLogger(QAbstractTestLogger *logger); static int loggerCount(); static bool loggerUsingStdout(); diff --git a/src/testlib/qtestresult.cpp b/src/testlib/qtestresult.cpp index 88028aac6e..2e89930776 100644 --- a/src/testlib/qtestresult.cpp +++ b/src/testlib/qtestresult.cpp @@ -251,7 +251,8 @@ bool QTestResult::verify(bool statement, const char *statementStr, { QTEST_ASSERT(statementStr); - char msg[1024] = {'\0'}; + char msg[1024]; + msg[0] = '\0'; if (QTestLog::verboseLevel() >= 2) { qsnprintf(msg, 1024, "QVERIFY(%s)", statementStr); @@ -309,7 +310,8 @@ static bool compareHelper(bool success, const char *failureMsg, bool hasValues = true) { const size_t maxMsgLen = 1024; - char msg[maxMsgLen] = {'\0'}; + char msg[maxMsgLen]; + msg[0] = '\0'; QTEST_ASSERT(expected); QTEST_ASSERT(actual); diff --git a/src/testlib/qtestutil_macos.mm b/src/testlib/qtestutil_macos.mm index 880cd0f91f..e6638e5cb8 100644 --- a/src/testlib/qtestutil_macos.mm +++ b/src/testlib/qtestutil_macos.mm @@ -53,8 +53,11 @@ namespace QTestPrivate { to start with a clean slate and prevents the "previous restore failed" dialog from showing if there was a test crash. */ - void disableWindowRestore() { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"ApplePersistenceIgnoreState"]; + void disableWindowRestore() + { + [NSUserDefaults.standardUserDefaults registerDefaults:@{ + @"ApplePersistenceIgnoreState" : @YES + }]; } bool macCrashReporterWillShowDialog() diff --git a/src/testlib/selfcover.pri b/src/testlib/selfcover.pri index 7de50ba6e6..eb9d5ac53c 100644 --- a/src/testlib/selfcover.pri +++ b/src/testlib/selfcover.pri @@ -1,5 +1,5 @@ # Configuration for testlib and its tests, to instrument with -# FrogLogic's Squish CoCo (cf. testcocoon.prf, which handles similar +# Coco (cf. testcocoon.prf, which handles similar # for general code; but testlib needs special handling). # Only for use when feature testlib_selfcover is enabled: @@ -13,7 +13,7 @@ COVERAGE_OPTIONS += --cs-include-file-abs-wildcard=*/src/testlib/* COVERAGE_OPTIONS += --cs-mcc # enable Multiple Condition Coverage COVERAGE_OPTIONS += --cs-mcdc # enable Multiple Condition / Decision Coverage # (recommended for ISO 26262 ASIL A, B and C -- highly recommended for ASIL D) -# https://doc.froglogic.com/squish-coco/4.1/codecoverage.html#sec%3Amcdc +# https://doc.qt.io/coco/code-coverage-analysis.html#mc-dc QMAKE_CFLAGS += $$COVERAGE_OPTIONS QMAKE_CXXFLAGS += $$COVERAGE_OPTIONS |