summaryrefslogtreecommitdiffstats
path: root/src/testlib/qteamcitylogger.cpp
diff options
context:
space:
mode:
authorBorgar Ovsthus <borgar.ovsthus@fmcti.com>2016-01-06 18:20:29 +0100
committerBorgar Øvsthus <borgar.ovsthus@fmcti.com>2016-01-23 18:30:58 +0000
commitfbd6acedac8e1f4ee624cb713055fcad1ceabf96 (patch)
tree37ab3e2e7ec82f5cd641c8ddfc38566b17b7fa72 /src/testlib/qteamcitylogger.cpp
parent9eda09f511246720a8c4eff2bbb52bbfacc36f48 (diff)
Add TeamCity logging feature to testlib
This allows TeamCity to parse realtime test-results instead of using post-build XML Report Processing. This does not support logging of benchmarks. [ChangeLog][QTest] Added a new logging mode that allow test-results to be parsed on-the-fly when using Jetbrains TeamCity as CI-server. This mode is enabled by using the -teamcity option on the command-line. Change-Id: Ie730beb643043eb0f448f99abe6c0b5ac48aaf03 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Diffstat (limited to 'src/testlib/qteamcitylogger.cpp')
-rw-r--r--src/testlib/qteamcitylogger.cpp292
1 files changed, 292 insertions, 0 deletions
diff --git a/src/testlib/qteamcitylogger.cpp b/src/testlib/qteamcitylogger.cpp
new file mode 100644
index 0000000000..4773ea937f
--- /dev/null
+++ b/src/testlib/qteamcitylogger.cpp
@@ -0,0 +1,292 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Borgar Ovsthus
+** 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 <QtTest/private/qtestresult_p.h>
+#include <QtTest/qtestassert.h>
+#include <QtTest/private/qtestlog_p.h>
+#include <QtTest/private/qteamcitylogger_p.h>
+#include <QtCore/qbytearray.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QTest {
+
+ static const char *incidentType2String(QAbstractTestLogger::IncidentTypes type)
+ {
+ switch (type) {
+ case QAbstractTestLogger::Pass:
+ return "PASS";
+ case QAbstractTestLogger::XFail:
+ return "XFAIL";
+ case QAbstractTestLogger::Fail:
+ return "FAIL!";
+ case QAbstractTestLogger::XPass:
+ return "XPASS";
+ case QAbstractTestLogger::BlacklistedPass:
+ return "BPASS";
+ case QAbstractTestLogger::BlacklistedFail:
+ return "BFAIL";
+ }
+ return "??????";
+ }
+
+ static const char *messageType2String(QAbstractTestLogger::MessageTypes type)
+ {
+ switch (type) {
+ case QAbstractTestLogger::Skip:
+ return "SKIP";
+ case QAbstractTestLogger::Warn:
+ return "WARNING";
+ case QAbstractTestLogger::QWarning:
+ return "QWARN";
+ case QAbstractTestLogger::QDebug:
+ return "QDEBUG";
+ case QAbstractTestLogger::QInfo:
+ return "QINFO";
+ case QAbstractTestLogger::QSystem:
+ return "QSYSTEM";
+ case QAbstractTestLogger::QFatal:
+ return "QFATAL";
+ case QAbstractTestLogger::Info:
+ return "INFO";
+ }
+ return "??????";
+ }
+}
+
+QTeamCityLogger::QTeamCityLogger(const char *filename)
+ : QAbstractTestLogger(filename)
+{
+}
+
+QTeamCityLogger::~QTeamCityLogger()
+{
+}
+
+void QTeamCityLogger::startLogging()
+{
+ QAbstractTestLogger::startLogging();
+
+ QString testSuiteName = tcEscapedString(QString::fromUtf8(QTestResult::currentTestObjectName()));
+
+ QString str = QString(QLatin1String("##teamcity[testSuiteStarted name='%1']\n")).arg(testSuiteName);
+ outputString(qPrintable(str));
+}
+
+void QTeamCityLogger::stopLogging()
+{
+ QString testSuiteName = tcEscapedString(QString::fromUtf8(QTestResult::currentTestObjectName()));
+
+ QString str = QString(QLatin1String("##teamcity[testSuiteFinished name='%1']\n")).arg(testSuiteName);
+ outputString(qPrintable(str));
+
+ QAbstractTestLogger::stopLogging();
+}
+
+void QTeamCityLogger::enterTestFunction(const char * /*function*/)
+{
+ // don't print anything
+}
+
+void QTeamCityLogger::leaveTestFunction()
+{
+ // don't print anything
+}
+
+void QTeamCityLogger::addIncident(IncidentTypes type, const char *description,
+ const char *file, int line)
+{
+ // suppress PASS and XFAIL in silent mode
+ if ((type == QAbstractTestLogger::Pass || type == QAbstractTestLogger::XFail) && QTestLog::verboseLevel() < 0)
+ return;
+
+ QString buf;
+
+ QString tmpFuncName = escapedTestFuncName();
+
+ if (tmpFuncName != currTestFuncName) {
+ buf = QString(QLatin1String("##teamcity[testStarted name='%1']\n")).arg(tmpFuncName);
+ outputString(qPrintable(buf));
+ }
+
+ currTestFuncName = tmpFuncName;
+
+ if (type == QAbstractTestLogger::XFail) {
+ addPendingMessage(QTest::incidentType2String(type), QString::fromUtf8(description), file, line);
+ return;
+ }
+
+ QString detailedText = QString::fromUtf8(description);
+ detailedText = tcEscapedString(detailedText);
+
+ // Test failed
+ if ((type == QAbstractTestLogger::Fail) || (type == QAbstractTestLogger::XPass)) {
+ QString messageText(QLatin1String("Failure!"));
+
+ if (file)
+ messageText += QString(QLatin1String(" |[Loc: %1(%2)|]")).arg(QString::fromUtf8(file)).arg(line);
+
+ buf = QString(QLatin1String("##teamcity[testFailed name='%1' message='%2' details='%3']\n"))
+ .arg(tmpFuncName)
+ .arg(messageText)
+ .arg(detailedText);
+
+ outputString(qPrintable(buf));
+ }
+
+ if (!pendingMessages.isEmpty()) {
+ buf = QString(QLatin1String("##teamcity[testStdOut name='%1' out='%2']\n"))
+ .arg(tmpFuncName)
+ .arg(pendingMessages);
+
+ outputString(qPrintable(buf));
+
+ pendingMessages.clear();
+ }
+
+ buf = QString(QLatin1String("##teamcity[testFinished name='%1']\n")).arg(tmpFuncName);
+ outputString(qPrintable(buf));
+}
+
+void QTeamCityLogger::addBenchmarkResult(const QBenchmarkResult &)
+{
+ // don't print anything
+}
+
+void QTeamCityLogger::addMessage(MessageTypes type, const QString &message,
+ const char *file, int line)
+{
+ // suppress non-fatal messages in silent mode
+ if (type != QAbstractTestLogger::QFatal && QTestLog::verboseLevel() < 0)
+ return;
+
+ QString escapedMessage = tcEscapedString(message);
+
+ QString buf;
+
+ if (type == QAbstractTestLogger::Skip) {
+ if (file)
+ escapedMessage.append(QString(QLatin1String(" |[Loc: %1(%2)|]")).arg(QString::fromUtf8(file)).arg(line));
+
+ buf = QString(QLatin1String("##teamcity[testIgnored name='%1' message='%2']\n"))
+ .arg(escapedTestFuncName())
+ .arg(escapedMessage);
+
+ outputString(qPrintable(buf));
+ }
+ else {
+ addPendingMessage(QTest::messageType2String(type), escapedMessage, file, line);
+ }
+}
+
+QString QTeamCityLogger::tcEscapedString(const QString &str) const
+{
+ QString formattedString;
+
+ for (int i = 0; i < str.length(); i++) {
+ QChar ch = str.at(i);
+
+ switch (ch.toLatin1()) {
+ case '\n':
+ formattedString.append(QLatin1Literal("|n"));
+ break;
+ case '\r':
+ formattedString.append(QLatin1Literal("|r"));
+ break;
+ case '|':
+ formattedString.append(QLatin1Literal("||"));
+ break;
+ case '[':
+ formattedString.append(QLatin1Literal("|["));
+ break;
+ case ']':
+ formattedString.append(QLatin1Literal("|]"));
+ break;
+ case '\'':
+ formattedString.append(QLatin1Literal("|'"));
+ break;
+ default:
+ formattedString.append(ch);
+ }
+ }
+
+ return qMove(formattedString).simplified();
+}
+
+QString QTeamCityLogger::escapedTestFuncName() const
+{
+ const char *fn = QTestResult::currentTestFunction() ? QTestResult::currentTestFunction()
+ : "UnknownTestFunc";
+ const char *tag = QTestResult::currentDataTag() ? QTestResult::currentDataTag() : "";
+
+ QString str = QString(QLatin1String("%1(%2)")).arg(QString::fromUtf8(fn)).arg(QString::fromUtf8(tag));
+ str = tcEscapedString(str);
+
+ return str;
+}
+
+void QTeamCityLogger::addPendingMessage(const char *type, const QString &msg, const char *file, int line)
+{
+ QString pendMessage;
+
+ if (!pendingMessages.isEmpty())
+ pendMessage += QLatin1String("|n");
+
+ if (file) {
+ pendMessage += QString(QLatin1String("%1 |[Loc: %2(%3)|]: %4"))
+ .arg(QString::fromUtf8(type))
+ .arg(QString::fromUtf8(file))
+ .arg(line)
+ .arg(msg);
+
+ }
+ else {
+ pendMessage += QString(QLatin1String("%1: %2"))
+ .arg(QString::fromUtf8(type))
+ .arg(msg);
+ }
+
+ pendingMessages.append(pendMessage);
+}
+
+QT_END_NAMESPACE