/**************************************************************************** ** ** Copyright (C) 2016 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$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_XCTEST) #include #endif #if defined(Q_OS_DARWIN) #include #endif #include #include #include #include #include #if QT_CONFIG(regularexpression) #include #endif #include #include #include QT_BEGIN_NAMESPACE static void saveCoverageTool(const char * appname, bool testfailed, bool installedTestCoverage) { #ifdef __COVERAGESCANNER__ # if QT_CONFIG(testlib_selfcover) __coveragescanner_teststate(QTestLog::failCount() > 0 ? "FAILED" : QTestLog::passCount() > 0 ? "PASSED" : "SKIPPED"); # else if (!installedTestCoverage) return; // install again to make sure the filename is correct. // without this, a plugin or similar may have changed the filename. __coveragescanner_install(appname); __coveragescanner_teststate(testfailed ? "FAILED" : "PASSED"); __coveragescanner_save(); __coveragescanner_testname(""); __coveragescanner_clear(); unsetenv("QT_TESTCOCOON_ACTIVE"); # endif // testlib_selfcover #else Q_UNUSED(appname); Q_UNUSED(testfailed); Q_UNUSED(installedTestCoverage); #endif } static QElapsedTimer elapsedFunctionTime; static QElapsedTimer elapsedTotalTime; #define FOREACH_TEST_LOGGER for (QAbstractTestLogger *logger : QTest::loggers) namespace QTest { int fails = 0; int passes = 0; int skips = 0; int blacklists = 0; struct IgnoreResultList { inline IgnoreResultList(QtMsgType tp, const QVariant &patternIn) : type(tp), pattern(patternIn) {} static inline void clearList(IgnoreResultList *&list) { while (list) { IgnoreResultList *current = list; list = list->next; delete current; } } static void append(IgnoreResultList *&list, QtMsgType type, const QVariant &patternIn) { QTest::IgnoreResultList *item = new QTest::IgnoreResultList(type, patternIn); if (!list) { list = item; return; } IgnoreResultList *last = list; for ( ; last->next; last = last->next) ; last->next = item; } static bool stringsMatch(const QString &expected, const QString &actual) { if (expected == actual) return true; // 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 == expected.leftRef(expected.length() - 1); return false; } inline bool matches(QtMsgType tp, const QString &message) const { return tp == type && (pattern.userType() == QMetaType::QString ? stringsMatch(pattern.toString(), message) : #if QT_CONFIG(regularexpression) pattern.toRegularExpression().match(message).hasMatch()); #else false); #endif } QtMsgType type; QVariant pattern; IgnoreResultList *next = nullptr; }; static IgnoreResultList *ignoreResultList = nullptr; static QVector loggers; static bool loggerUsingStdout = false; static int verbosity = 0; static int maxWarnings = 2002; static bool installedTestCoverage = true; static QtMessageHandler oldMessageHandler; static bool handleIgnoredMessage(QtMsgType type, const QString &message) { if (!ignoreResultList) return false; IgnoreResultList *last = nullptr; IgnoreResultList *list = ignoreResultList; while (list) { if (list->matches(type, message)) { // remove the item from the list if (last) last->next = list->next; else if (list->next) ignoreResultList = list->next; else ignoreResultList = nullptr; delete list; return true; } last = list; list = list->next; } 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 this goes wrong, something is seriously broken. qInstallMessageHandler(oldMessageHandler); QTEST_ASSERT(QTestLog::loggerCount() != 0); } if (handleIgnoredMessage(type, message)) { // the message is expected, so just swallow it. return; } if (type != QtFatalMsg) { if (counter.loadRelaxed() <= 0) return; if (!counter.deref()) { FOREACH_TEST_LOGGER { logger->addMessage(QAbstractTestLogger::QSystem, QStringLiteral("Maximum amount of warnings exceeded. Use -maxwarnings to override.")); } return; } } FOREACH_TEST_LOGGER logger->addMessage(type, context, message); if (type == QtFatalMsg) { /* Right now, we're inside the custom message handler and we're * being qt_message_output in qglobal.cpp. After we return from * 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); QTestLog::leaveTestFunction(); QTestLog::stopLogging(); } } } void QTestLog::enterTestFunction(const char* function) { elapsedFunctionTime.restart(); if (printAvailableTags) return; QTEST_ASSERT(function); FOREACH_TEST_LOGGER logger->enterTestFunction(function); } void QTestLog::enterTestData(QTestData *data) { QTEST_ASSERT(data); FOREACH_TEST_LOGGER logger->enterTestData(data); } int QTestLog::unhandledIgnoreMessages() { int i = 0; QTest::IgnoreResultList *list = QTest::ignoreResultList; while (list) { ++i; list = list->next; } return i; } void QTestLog::leaveTestFunction() { if (printAvailableTags) return; FOREACH_TEST_LOGGER logger->leaveTestFunction(); } void QTestLog::printUnhandledIgnoreMessages() { 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('"'); } else { #if QT_CONFIG(regularexpression) message = QStringLiteral("Did not receive any message matching: \"") + list->pattern.toRegularExpression().pattern() + QLatin1Char('"'); #endif } FOREACH_TEST_LOGGER logger->addMessage(QAbstractTestLogger::Info, message); list = list->next; } } void QTestLog::clearIgnoreMessages() { QTest::IgnoreResultList::clearList(QTest::ignoreResultList); } void QTestLog::addPass(const char *msg) { if (printAvailableTags) return; QTEST_ASSERT(msg); ++QTest::passes; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::Pass, msg); } void QTestLog::addFail(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); ++QTest::fails; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::Fail, msg, file, line); } void QTestLog::addXFail(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); QTEST_ASSERT(file); FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::XFail, msg, file, line); } void QTestLog::addXPass(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); QTEST_ASSERT(file); ++QTest::fails; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::XPass, msg, file, line); } void QTestLog::addBPass(const char *msg) { QTEST_ASSERT(msg); ++QTest::blacklists; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::BlacklistedPass, msg); } void QTestLog::addBFail(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); QTEST_ASSERT(file); ++QTest::blacklists; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::BlacklistedFail, msg, file, line); } void QTestLog::addBXPass(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); QTEST_ASSERT(file); ++QTest::blacklists; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::BlacklistedXPass, msg, file, line); } void QTestLog::addBXFail(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); QTEST_ASSERT(file); ++QTest::blacklists; FOREACH_TEST_LOGGER logger->addIncident(QAbstractTestLogger::BlacklistedXFail, msg, file, line); } void QTestLog::addSkip(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); QTEST_ASSERT(file); ++QTest::skips; FOREACH_TEST_LOGGER logger->addMessage(QAbstractTestLogger::Skip, QString::fromUtf8(msg), file, line); } void QTestLog::addBenchmarkResult(const QBenchmarkResult &result) { FOREACH_TEST_LOGGER logger->addBenchmarkResult(result); } void QTestLog::startLogging() { elapsedTotalTime.start(); elapsedFunctionTime.start(); FOREACH_TEST_LOGGER logger->startLogging(); QTest::oldMessageHandler = qInstallMessageHandler(QTest::messageHandler); } void QTestLog::stopLogging() { qInstallMessageHandler(QTest::oldMessageHandler); FOREACH_TEST_LOGGER { logger->stopLogging(); delete logger; } QTest::loggers.clear(); QTest::loggerUsingStdout = false; saveCoverageTool(QTestResult::currentAppName(), failCount() != 0, QTestLog::installedTestCoverage()); } 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) { case QTestLog::Plain: logger = new QPlainTestLogger(filename); break; case QTestLog::CSV: logger = new QCsvBenchmarkLogger(filename); break; case QTestLog::XML: logger = new QXmlTestLogger(QXmlTestLogger::Complete, filename); break; case QTestLog::LightXML: logger = new QXmlTestLogger(QXmlTestLogger::Light, filename); break; case QTestLog::XunitXML: logger = new QXunitTestLogger(filename); break; case QTestLog::TeamCity: logger = new QTeamCityLogger(filename); break; case QTestLog::TAP: logger = new QTapTestLogger(filename); break; #if defined(QT_USE_APPLE_UNIFIED_LOGGING) case QTestLog::Apple: logger = new QAppleTestLogger; break; #endif #if defined(HAVE_XCTEST) case QTestLog::XCTest: logger = new QXcodeTestLogger; break; #endif } QTEST_ASSERT(logger); QTest::loggers.append(logger); } int QTestLog::loggerCount() { return QTest::loggers.size(); } bool QTestLog::loggerUsingStdout() { return QTest::loggerUsingStdout; } void QTestLog::warn(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); FOREACH_TEST_LOGGER logger->addMessage(QAbstractTestLogger::Warn, QString::fromUtf8(msg), file, line); } void QTestLog::info(const char *msg, const char *file, int line) { QTEST_ASSERT(msg); FOREACH_TEST_LOGGER logger->addMessage(QAbstractTestLogger::Info, QString::fromUtf8(msg), file, line); } void QTestLog::setVerboseLevel(int level) { QTest::verbosity = level; } int QTestLog::verboseLevel() { return QTest::verbosity; } void QTestLog::ignoreMessage(QtMsgType type, const char *msg) { QTEST_ASSERT(msg); QTest::IgnoreResultList::append(QTest::ignoreResultList, type, QString::fromLocal8Bit(msg)); } #if QT_CONFIG(regularexpression) void QTestLog::ignoreMessage(QtMsgType type, const QRegularExpression &expression) { QTEST_ASSERT(expression.isValid()); QTest::IgnoreResultList::append(QTest::ignoreResultList, type, QVariant(expression)); } #endif // QT_CONFIG(regularexpression) void QTestLog::setMaxWarnings(int m) { QTest::maxWarnings = m <= 0 ? INT_MAX : m + 2; } bool QTestLog::printAvailableTags = false; void QTestLog::setPrintAvailableTagsMode() { printAvailableTags = true; } int QTestLog::passCount() { return QTest::passes; } int QTestLog::failCount() { return QTest::fails; } int QTestLog::skipCount() { return QTest::skips; } int QTestLog::blacklistCount() { return QTest::blacklists; } int QTestLog::totalCount() { return passCount() + failCount() + skipCount() + blacklistCount(); } void QTestLog::resetCounters() { QTest::passes = 0; QTest::fails = 0; QTest::skips = 0; } void QTestLog::setInstalledTestCoverage(bool installed) { QTest::installedTestCoverage = installed; } bool QTestLog::installedTestCoverage() { return QTest::installedTestCoverage; } qint64 QTestLog::nsecsTotalTime() { return elapsedTotalTime.nsecsElapsed(); } qint64 QTestLog::nsecsFunctionTime() { return elapsedFunctionTime.nsecsElapsed(); } QT_END_NAMESPACE