diff options
Diffstat (limited to 'src/testlib/qtestlog.cpp')
-rw-r--r-- | src/testlib/qtestlog.cpp | 273 |
1 files changed, 194 insertions, 79 deletions
diff --git a/src/testlib/qtestlog.cpp b/src/testlib/qtestlog.cpp index 3341c235e6..929ccb370b 100644 --- a/src/testlib/qtestlog.cpp +++ b/src/testlib/qtestlog.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtTest module 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) 2021 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 <QtTest/qtestassert.h> @@ -60,6 +24,7 @@ #include <QtCore/qbytearray.h> #include <QtCore/qelapsedtimer.h> #include <QtCore/qlist.h> +#include <QtCore/qmutex.h> #include <QtCore/qvariant.h> #if QT_CONFIG(regularexpression) #include <QtCore/QRegularExpression> @@ -68,9 +33,15 @@ #include <stdlib.h> #include <string.h> #include <limits.h> +#include <vector> + +#include <vector> +#include <memory> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + static void saveCoverageTool(const char * appname, bool testfailed, bool installedTestCoverage) { #ifdef __COVERAGESCANNER__ @@ -96,10 +67,10 @@ static void saveCoverageTool(const char * appname, bool testfailed, bool install #endif } -static QElapsedTimer elapsedFunctionTime; -static QElapsedTimer elapsedTotalTime; +Q_CONSTINIT static QElapsedTimer elapsedFunctionTime; +Q_CONSTINIT static QElapsedTimer elapsedTotalTime; -#define FOREACH_TEST_LOGGER for (QAbstractTestLogger *logger : QTest::loggers) +#define FOREACH_TEST_LOGGER for (const auto &logger : std::as_const(*QTest::loggers())) namespace QTest { @@ -107,6 +78,7 @@ namespace QTest { int passes = 0; int skips = 0; int blacklists = 0; + enum { Unresolved, Passed, Skipped, Suppressed, Failed } currentTestState; struct IgnoreResultList { @@ -143,8 +115,8 @@ namespace QTest { // ignore an optional whitespace at the end of str // (the space was added automatically by ~QDebug() until Qt 5.3, // so autotests still might expect it) - if (expected.endsWith(QLatin1Char(' '))) - return actual == QStringView{expected}.left(expected.length() - 1); + if (expected.endsWith(u' ')) + return actual == QStringView{expected}.left(expected.size() - 1); return false; } @@ -167,9 +139,11 @@ namespace QTest { }; static IgnoreResultList *ignoreResultList = nullptr; + Q_CONSTINIT static QBasicMutex mutex; - static QList<QAbstractTestLogger *> loggers; - static bool loggerUsingStdout = false; + static std::vector<QVariant> failOnWarningList; + + Q_GLOBAL_STATIC(std::vector<std::unique_ptr<QAbstractTestLogger>>, loggers) static int verbosity = 0; static int maxWarnings = 2002; @@ -179,6 +153,8 @@ namespace QTest { static bool handleIgnoredMessage(QtMsgType type, const QString &message) { + const QMutexLocker mutexLocker(&QTest::mutex); + if (!ignoreResultList) return false; IgnoreResultList *last = nullptr; @@ -188,10 +164,8 @@ namespace QTest { // remove the item from the list if (last) last->next = list->next; - else if (list->next) - ignoreResultList = list->next; else - ignoreResultList = nullptr; + ignoreResultList = list->next; delete list; return true; @@ -203,14 +177,40 @@ namespace QTest { return false; } + static bool handleFailOnWarning(const QMessageLogContext &context, const QString &message) + { + // failOnWarning can be called multiple times per test function, so let + // each call cause a failure if required. + for (const auto &pattern : failOnWarningList) { + if (pattern.metaType() == QMetaType::fromType<QString>()) { + if (message != pattern.toString()) + continue; + } +#if QT_CONFIG(regularexpression) + else if (pattern.metaType() == QMetaType::fromType<QRegularExpression>()) { + if (!message.contains(pattern.toRegularExpression())) + continue; + } +#endif + + const size_t maxMsgLen = 1024; + char msg[maxMsgLen] = {'\0'}; + qsnprintf(msg, maxMsgLen, "Received a warning that resulted in a failure:\n%s", + qPrintable(message)); + QTestResult::addFailure(msg, context.file, context.line); + return true; + } + return false; + } + static void messageHandler(QtMsgType type, const QMessageLogContext & context, const QString &message) { static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(QTest::maxWarnings); - if (QTestLog::loggerCount() == 0) { + if (!QTestLog::hasLoggers()) { // if this goes wrong, something is seriously broken. qInstallMessageHandler(oldMessageHandler); - QTEST_ASSERT(QTestLog::loggerCount() != 0); + QTEST_ASSERT(QTestLog::hasLoggers()); } if (handleIgnoredMessage(type, message)) { @@ -218,13 +218,16 @@ namespace QTest { return; } + if (type == QtWarningMsg && handleFailOnWarning(context, message)) + return; + if (type != QtFatalMsg) { if (counter.loadRelaxed() <= 0) return; if (!counter.deref()) { FOREACH_TEST_LOGGER { - logger->addMessage(QAbstractTestLogger::QSystem, + logger->addMessage(QAbstractTestLogger::Warn, QStringLiteral("Maximum amount of warnings exceeded. Use -maxwarnings to override.")); } return; @@ -240,7 +243,7 @@ namespace QTest { * this function, it will proceed with calling exit() and abort() * and hence crash. Therefore, we call these logging functions such * that we wrap up nicely, and in particular produce well-formed XML. */ - QTestResult::addFailure("Received a fatal error.", "Unknown file", 0); + QTestResult::addFailure("Received a fatal error.", context.file, context.line); QTestLog::leaveTestFunction(); QTestLog::stopLogging(); } @@ -269,6 +272,7 @@ void QTestLog::enterTestData(QTestData *data) int QTestLog::unhandledIgnoreMessages() { + const QMutexLocker mutexLocker(&QTest::mutex); int i = 0; QTest::IgnoreResultList *list = QTest::ignoreResultList; while (list) { @@ -289,14 +293,16 @@ void QTestLog::leaveTestFunction() void QTestLog::printUnhandledIgnoreMessages() { + const QMutexLocker mutexLocker(&QTest::mutex); QString message; QTest::IgnoreResultList *list = QTest::ignoreResultList; while (list) { if (list->pattern.userType() == QMetaType::QString) { - message = QStringLiteral("Did not receive message: \"") + list->pattern.toString() + QLatin1Char('"'); + message = "Did not receive message: \"%1\""_L1.arg(list->pattern.toString()); } else { #if QT_CONFIG(regularexpression) - message = QStringLiteral("Did not receive any message matching: \"") + list->pattern.toRegularExpression().pattern() + QLatin1Char('"'); + message = "Did not receive any message matching: \"%1\""_L1.arg( + list->pattern.toRegularExpression().pattern()); #endif } FOREACH_TEST_LOGGER @@ -308,17 +314,32 @@ void QTestLog::printUnhandledIgnoreMessages() void QTestLog::clearIgnoreMessages() { + const QMutexLocker mutexLocker(&QTest::mutex); QTest::IgnoreResultList::clearList(QTest::ignoreResultList); } +void QTestLog::clearFailOnWarnings() +{ + QTest::failOnWarningList.clear(); +} + +void QTestLog::clearCurrentTestState() +{ + clearIgnoreMessages(); + clearFailOnWarnings(); + QTest::currentTestState = QTest::Unresolved; +} + void QTestLog::addPass(const char *msg) { if (printAvailableTags) return; QTEST_ASSERT(msg); + Q_ASSERT(QTest::currentTestState == QTest::Unresolved); ++QTest::passes; + QTest::currentTestState = QTest::Passed; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::Pass, msg); @@ -328,8 +349,18 @@ void QTestLog::addFail(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); - ++QTest::fails; + if (QTest::currentTestState == QTest::Unresolved) { + ++QTest::fails; + } else { + // After an XPASS/Continue, or fail or skip in a function the test + // calls, we can subsequently fail. + Q_ASSERT(QTest::currentTestState == QTest::Failed + || QTest::currentTestState == QTest::Skipped); + } + // It is up to particular loggers to decide whether to report such + // subsequent failures; they may carry useful information. + QTest::currentTestState = QTest::Failed; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::Fail, msg, file, line); } @@ -337,7 +368,8 @@ void QTestLog::addFail(const char *msg, const char *file, int line) 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); @@ -346,10 +378,17 @@ void QTestLog::addXFail(const char *msg, const char *file, int line) void QTestLog::addXPass(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); - QTEST_ASSERT(file); - ++QTest::fails; + if (QTest::currentTestState == QTest::Unresolved) { + ++QTest::fails; + } else { + // After an XPASS/Continue, we can subsequently XPASS again. + // Likewise after a fail or skip in a function called by the test. + Q_ASSERT(QTest::currentTestState == QTest::Failed + || QTest::currentTestState == QTest::Skipped); + } + QTest::currentTestState = QTest::Failed; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::XPass, msg, file, line); } @@ -357,8 +396,10 @@ void QTestLog::addXPass(const char *msg, const char *file, int line) void QTestLog::addBPass(const char *msg) { QTEST_ASSERT(msg); + Q_ASSERT(QTest::currentTestState == QTest::Unresolved); - ++QTest::blacklists; + ++QTest::blacklists; // Not passes ? + QTest::currentTestState = QTest::Suppressed; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::BlacklistedPass, msg); @@ -367,10 +408,17 @@ void QTestLog::addBPass(const char *msg) void QTestLog::addBFail(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); - QTEST_ASSERT(file); - ++QTest::blacklists; + if (QTest::currentTestState == QTest::Unresolved) { + ++QTest::blacklists; + } else { + // After a BXPASS/Continue, we can subsequently fail. + // Likewise after a fail or skip in a function called by a test. + Q_ASSERT(QTest::currentTestState == QTest::Suppressed + || QTest::currentTestState == QTest::Skipped); + } + QTest::currentTestState = QTest::Suppressed; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::BlacklistedFail, msg, file, line); } @@ -378,10 +426,17 @@ void QTestLog::addBFail(const char *msg, const char *file, int line) void QTestLog::addBXPass(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); - QTEST_ASSERT(file); - ++QTest::blacklists; + if (QTest::currentTestState == QTest::Unresolved) { + ++QTest::blacklists; + } else { + // After a BXPASS/Continue, we may BXPASS again. + // Likewise after a fail or skip in a function called by a test. + Q_ASSERT(QTest::currentTestState == QTest::Suppressed + || QTest::currentTestState == QTest::Skipped); + } + QTest::currentTestState = QTest::Suppressed; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::BlacklistedXPass, msg, file, line); } @@ -389,9 +444,8 @@ void QTestLog::addBXPass(const char *msg, const char *file, int line) 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); @@ -400,18 +454,28 @@ void QTestLog::addBXFail(const char *msg, const char *file, int line) void QTestLog::addSkip(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); - QTEST_ASSERT(file); - ++QTest::skips; + if (QTest::currentTestState == QTest::Unresolved) { + ++QTest::skips; + QTest::currentTestState = QTest::Skipped; + } else { + // After an B?XPASS/Continue, we might subsequently skip. + // Likewise after a skip in a function called by a test. + Q_ASSERT(QTest::currentTestState == QTest::Suppressed + || QTest::currentTestState == QTest::Failed + || QTest::currentTestState == QTest::Skipped); + } + // It is up to particular loggers to decide whether to report such + // subsequent skips; they may carry useful information. FOREACH_TEST_LOGGER - logger->addMessage(QAbstractTestLogger::Skip, QString::fromUtf8(msg), file, line); + logger->addIncident(QAbstractTestLogger::Skip, msg, file, line); } -void QTestLog::addBenchmarkResult(const QBenchmarkResult &result) +void QTestLog::addBenchmarkResults(const QList<QBenchmarkResult> &results) { FOREACH_TEST_LOGGER - logger->addBenchmarkResult(result); + logger->addBenchmarkResults(results); } void QTestLog::startLogging() @@ -428,10 +492,8 @@ void QTestLog::stopLogging() qInstallMessageHandler(QTest::oldMessageHandler); FOREACH_TEST_LOGGER { logger->stopLogging(); - delete logger; } - QTest::loggers.clear(); - QTest::loggerUsingStdout = false; + QTest::loggers()->clear(); saveCoverageTool(QTestResult::currentAppName(), failCount() != 0, QTestLog::installedTestCoverage()); } @@ -439,8 +501,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 +538,51 @@ void QTestLog::addLogger(LogMode mode, const char *filename) } QTEST_ASSERT(logger); - QTest::loggers.append(logger); + addLogger(logger); } -int QTestLog::loggerCount() +/*! + \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()->emplace_back(logger); +} + +bool QTestLog::hasLoggers() { - return QTest::loggers.size(); + return !QTest::loggers()->empty(); +} + +/*! + \internal + + Returns true if all loggers support repeated test runs +*/ +bool QTestLog::isRepeatSupported() +{ + FOREACH_TEST_LOGGER { + if (!logger->isRepeatSupported()) + return false; + } + + return true; } 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) @@ -521,6 +615,7 @@ void QTestLog::ignoreMessage(QtMsgType type, const char *msg) { QTEST_ASSERT(msg); + const QMutexLocker mutexLocker(&QTest::mutex); QTest::IgnoreResultList::append(QTest::ignoreResultList, type, QString::fromUtf8(msg)); } @@ -529,10 +624,30 @@ void QTestLog::ignoreMessage(QtMsgType type, const QRegularExpression &expressio { QTEST_ASSERT(expression.isValid()); + const QMutexLocker mutexLocker(&QTest::mutex); QTest::IgnoreResultList::append(QTest::ignoreResultList, type, QVariant(expression)); } #endif // QT_CONFIG(regularexpression) +void QTestLog::failOnWarning() +{ + QTest::failOnWarningList.push_back({}); +} + +void QTestLog::failOnWarning(const char *msg) +{ + QTest::failOnWarningList.push_back(QString::fromUtf8(msg)); +} + +#if QT_CONFIG(regularexpression) +void QTestLog::failOnWarning(const QRegularExpression &expression) +{ + QTEST_ASSERT(expression.isValid()); + + QTest::failOnWarningList.push_back(QVariant::fromValue(expression)); +} +#endif // QT_CONFIG(regularexpression) + void QTestLog::setMaxWarnings(int m) { QTest::maxWarnings = m <= 0 ? INT_MAX : m + 2; |